数据结构的学习,从浅到深的学习,在了解他们的概念后,当然要明白他们分别是怎么实现的,同时通过对比来了解他们的异同点。
一.数据结构
1.1 什么是数据结构
所谓数据结构,拆开来讲,就是数据和结构。
数据:所有能输入到计算机中并且能被程序识别和处理的符号集合,包括数值数据(整数,实数),非数值型数据(图形,声音,文字)。
结构:数据之间的关系
数据对象:相同特性的数据元素的集合
数据元素:数据的基本单位
数据项:数据元素的最小单位
关系图:
最基础的数据结构:数组
1.2 数据结构三要素
1.2.1逻辑结构
集合:数据和元素之间没有关系
线性结构:数据和元素之间一对一
树结构:数据和元素之间一对多
图结构:数据和元素之间多对多
1.2.2 物理结构(存储结构)
顺序存储结构
链接存储结构
1.2.3 数据的运算
二.线性表
我们先引入一个概念:线性表。线性表是n个具有相同特性的数据元素的有限序列
常见的线性表:循序表,链表,栈,队列,字符串...
1. 顺序表
顺序表的底层结构是数组
1.1顺序表的实现
1.1.1静态顺序表和动态顺序表
静态顺序表
//给定的数组长度,如果不够,会导致后续的数据保存失败
#define N 100//长度通过宏的方式进行优化 使用100太过于麻烦,通过N代替100
typedef int SLDataType;//存储的也不一定是整型 typedef a b;就是用b代替a//
//struct SeqList
//{
//int a[100];//固定的长度 不代表是一个有效的数据
//int a[N];//优化上一行
//SLDataType a[N];//优化上一行
//int size;//有效的数据的个数
//};
动态顺序表
struct SeqList
{
SLDataType* arr;//存储数据的底层结构
int capacity;//记录顺序表的空间大小 即申请到的实际空间大小 有空间不一定有实际数据
int size;//记录顺序表当前有效的数据个数
//capacity是最大空间,size是已经使用了的空间
};
静态顺序表就是给定长度的数组int arr[10]
动态顺序表就是动态设置数组长度int *arr
1.1.2 初始化和销毁
这里的SL* ps是数组指针
头文件SeqList.h中定义
void SLInit(SL* ps);
SeqList.c的代码
//初始化和销毁
void SLInit(SL* ps)
{
ps->arr = NULL;//ps.arr = NULL;
ps->size = ps->capacity = 0;//ps.size = ps`.capacity = 0;
}
void SLDestroy(SL* ps)
{
assert(ps);
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
初始化,将结构体里面的数组置为NULL,值变量(有效数据个数,空间大小)置为0
1.1.3打印
头文件SeqList.h中定义
void SLPrint(SL* ps);//打印 *ps是为了保持接口一致性
SeqList.c的代码
//void SLPrint(SL * ps)//顺序表的打印
//{
// for (int i = 0; i < ps->size; i++)
// {
// printf("%d ", ps->arr[i]);
// }
// printf("\n");
//
//}
1.1.4扩容
头文件SeqList.h中定义
//扩容
//void SLCheckCapacity(SL* ps)
SeqList.c的代码
//void SLCheckCapacity(SL* ps)//扩容判断
//{
// if (ps->size == ps->capacity)
// {
// //ps->arr = (SLDataType*)realloc(ps->arr, 2 * ps->capacity);有问题 在上面capacity已经赋值为0
// int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//创建一个新的变量newcapacity
// //如果capacity是0,那么值是4 4是比特的意思
// //ps->arr = (SLDataType*)realloc(ps->arr, newcapacity);
// SLDataType* tmp = ps->arr = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));
// //临时指针指向开辟的空间
// if (tmp == NULL)
// {
// perror("realloc fail!");
// exit(1);//扩容失败直接中断程序
// }
// //扩容成功
// //free(ps->arr);//此时arr里面已经有了空间,要先释放
// //不对,这是realloc做的事情
// ps->arr = tmp;
// ps->capacity = newcapacity;//此时容量已经变了,变成了newcapacity
// }
//}
1.1.5头插/尾插
头文件SeqList.h中定义
//顺序表的头部/尾部插入
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
SeqList.c的代码
//顺序表的头部/尾部插入
void SLPushBack(SL* ps, SLDataType x) {
//断言--粗暴的解决方式
//assert(ps != NULL);
assert(ps);
//if判断--温柔的解决方式
//if (ps == NULL) {
// return;
//}
//空间不够,扩容
SLCheckCapacity(ps);
//空间足够,直接插入
ps->arr[ps->size++] = x;
//ps->size++;
}
void SLPushFront(SL* ps, SLDataType x) {
assert(ps);
//判断是否扩容
SLCheckCapacity(ps);
//旧数据往后挪动一位
for (int i = ps->size; i > 0; i--) //i = 1
{
ps->arr[i] = ps->arr[i - 1]; //ps->arr[1] = ps->arr[0]
}
ps->arr[0] = x;
ps->size++;
}
1.1.6头删/尾删
头文件SeqList.h中定义
//顺序表的头部/尾部删除
void SLPopBack(SL* ps);
void SLPopFront(SL* ps);
SeqList.c的代码
//顺序表的头部/尾部删除
void SLPopBack(SL* ps) {
assert(ps);
assert(ps->size);
//顺序表不为空
//ps->arr[ps->size - 1] = -1;
ps->size--;
}
void SLPopFront(SL* ps) {
assert(ps);
assert(ps->size);
//不为空执行挪动操作
for (int i = 0; i < ps->size-1 ;i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
1.1.7在指定位置之前插入数据/删除指定位置数据
头文件SeqList.h中定义
/指定位置之前插入数据
//删除指定位置数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
SeqList.c的代码
//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x) {
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
//pos及之后的数据往后挪动一位,pos空出来
for (int i = ps->size; i > pos ;i--)
{
ps->arr[i] = ps->arr[i - 1]; //ps->arr[pos+1] = ps->arr[pos]
}
ps->arr[pos] = x;
ps->size++;
}
//删除指定位置数据
void SLErase(SL* ps, int pos) {
assert(ps);
assert(pos >= 0 && pos < ps->size);
//pos以后的数据往前挪动一位
for (int i = pos;i < ps->size-1;i++)
{
ps->arr[i] = ps->arr[i + 1];//ps->arr[i-2] = ps->arr[i-1];
}
ps->size--;
}
1.1.8在顺序表中查找x
头文件SeqList.h中定义
//查找
int SLFind(SL* ps, SLDataType x);
SeqList.c的代码
在顺序表中查找x
//int SLFind(SL* ps, SLDataType x)
//{
// for (int i = 0; i < ps->size; i ++)
// {
// if (ps->arr[i] == x)
// {
// return i;
// }
// }
// return -1;
//}
2. 链表
链表是由一个一个节点组成的
一个节点包括数据和指针
2.1单向链表
typedef int SLTDataType;
//链表由节点组成
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
} SLTNode;
2.2.1头插/尾插
//尾插
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);//*pphead是指向第一个节点的地址
//SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
//newnode->data = x;
//newnode->next = NULL;
// //建立新节点
SLTNode* newnode = SLTBuyNode(x);
// //链表为空,新节点作为phead
if (*pphead == NULL)
{
*pphead = newnode;
return;
}
// //链表不为空,找尾节点
// //遍历链表,建立一个临时指针
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
//跳出循环,说明此时的ptail就是尾节点
ptail->next = newnode;
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
//newnode *pphead
newnode->next = *pphead;
*pphead = newnode;
}
2.2.2头删/尾删
//尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);//链表不能为空
//链表只有一个节点,有多个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
//销毁尾节点
free(ptail);
ptail = NULL;
}
/头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
//链表不能为空
assert(*pphead);
//让第二个节点成为新的头
//把旧的头节点释放掉
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
2.2.3查找
//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//遍历链表
SLTNode* pcur = *pphead;
while (pcur)//等价于pcur != NULL
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//没有找到
return NULL;
}
2.2.4在指定位置之前插入数据/在指定位置之后插入数据
//在指定位置之前插入数据
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
//要加上链表不能为空 pos不能为空,而pos在链表里面,因此链表也不能为空
assert(*pphead);
//pos刚好是头节点
if (pos == *pphead)
{
//头插
SLTPushFront(pphead, x);
return;
}
//pos不是头节点
SLTNode* prev = *pphead;//建立一个临时地址
SLTNode* newnode = SLTBuyNode(x);//申请一个新节点
while (prev->next != pos)
{
prev = prev->next;
}
//prev->next->newnode
prev->next = newnode;
newnode->next = pos;
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);//申请新节点
//pos newnode pos->next
newnode->next = pos->next;
pos->next = newnode;
}
2.2.5删除pos节点/删除pos之后的节点
//删除指定位置的节点pos
void SLTEraseBefore(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
//pos刚好是头节点,没有前驱节点,执行头删
if (*pphead == pos)
{
//头删
SLTPopFront(pphead);
return;
}
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//prev pos pos->next
prev->next = pos->next;
free(pos);
pos = NULL;
}
/删除指定位置之后的节点pos
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
//pos pos->next pos->next->next
SLTNode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
2.2.6打印/销毁链表
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;//pcur是一个临时指针
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
//销毁链表
void SlistDesTroy(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
2.2 双向链表
//定义双向链表中节点的结构
typedef int LTDataType;
typedef struct ListNode
{
int data;
struct ListNode* prev;
struct ListNode* next;
}LTNode;
带头双向循环链表
2.2.1头插/尾插
//尾插
void LTPushBack(LTNode* phead, LTDataType x) {
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead phead->prev(ptail) newnode
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x) {
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead newnode phead->next
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
void LTPrint(LTNode* phead) {
//phead不能为空
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
2.2.2头删/尾删
//尾删
void LTPopBack(LTNode* phead) {
assert(phead);
//链表为空:只有一个哨兵位节点
assert(phead->next != phead);
LTNode* del = phead->prev;
LTNode* prev = del->prev;
prev->next = phead;
phead->prev = prev;
free(del);
del = NULL;
}
/头删
void LTPopFront(LTNode* phead) {
assert(phead);
assert(phead->next != phead);
LTNode* del = phead->next;
LTNode* next = del->next;
//phead del next
next->prev = phead;
phead->next = next;
free(del);
del = NULL;
}
LTNode* LTFind(LTNode* phead, LTDataType x) {
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x) {
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
2.2.3查找
LTNode* LTFind(LTNode* phead, LTDataType x) {
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x) {
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
2.2.4在指定位置之前插入数据/在指定位置之后插入数据
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x) {
assert(pos);
LTNode* newnode = LTBuyNode(x);
//pos newnode pos->next
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
2.2.5删除pos节点/删除pos之后的节点
//删除pos位置的数据
void LTErase(LTNode* pos) {
assert(pos);
//pos->prev pos pos->next
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
2.2.6打印/销毁链表
void LTDesTroy(LTNode* phead) {
//哨兵位不能为空
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//链表中只有一个哨兵位
free(phead);
phead = NULL;
}