在最开始学习数据结构,首先接触到的是关于复杂度的解释,这里包含了时间复杂度和空间复杂度,有了这个相关概念之后,进一步深入到数据结构的基本几种结构。首先要知道什么是线性表,线性表:是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串.....
这里要注意一个问题,我们认为线性表是像一条线一样的连接在一起,这样的认识是逻辑上的,那么在物理层面上来看,并非是连续的方式存储,这是由于内存的开辟有时并非是连续的,例如当原来的空间不够时,那么就需要另外开辟空间,此时开辟的空间和原来的空间在逻辑上是连续的,而在物理上就不一定是连续的了。
那么线性表在物理结构上存储通常就是以数组或者链式结构存储——也就是顺序表和链表。
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;
//使用一个整形指针指向顺序表的数组
int size;
//记录有效数据
int capacity;
//空间容量--如何扩容具体问题具体分析,一般选择是原来的二倍
}SL;
直接上结构,从上述的结构我们可很清洗的看出来使用 C 语言定义的顺序表,我们这里使用重命名的方式定义一个顺序表的指针的目的是为了使我们的顺序表可以存储其他类型的数据,只需要改变重定义的 int 即可。另外需要注意的是,定义的顺序表是一个可变的顺序表结构,也就是说当顺序表的内容满之后,会自动进行扩容以满足需求。
下面是顺序表的各种操作的函数代码,以及一些使用顺序表的案例
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
//扩容
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newCapacity);
//可以对空进行扩容
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;//防止扩容失败把原空间给覆盖
ps->capacity = newCapacity;
}
}
void SLInit(SL* ps)
{
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
void SLDestroy(SL* ps)
{
assert(ps);
if (ps->a != NULL)
{
free(ps->a);
//free崩溃可能是指针释放位置不对
//第二种就是对空间的访问有越界
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
}
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
//尾插
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
//头插
SLCheckCapacity(ps);
//每次插入一个数据,所以每次把顺序表往后挪动一个位置就可以
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
void SLPopBack(SL* ps)
{
assert(ps);
//尾删
//空则不进行删除
assert(ps->size > 0);
ps->size--;
}
void SLPopFront(SL* ps)
{
//头删
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[pos] = x;
ps->size++;
}
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
void SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
void SLPrint(SL* ps)
{
assert(ps);
for (size_t i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
void Test1()
{
SL s1;
SLInit(&s1);//传址方式,才可以对顺序表进行操作
SLPushBack(&s1, 1);
SLPushBack(&s1, 2);
SLPushBack(&s1, 3);
SLPushBack(&s1, 4);
SLPushBack(&s1, 5);
SLPushBack(&s1, 6);
SLPushBack(&s1, 7);
SLPushFront(&s1, 20);
SLPopBack(&s1);
SLPopFront(&s1);
SLPrint(&s1);
}
void Test2()
{
SL s2;
SLInit(&s2);
SLPushBack(&s2, 1);
SLPushBack(&s2, 2);
SLPushBack(&s2, 3);
SLPushBack(&s2, 4);
SLPrint(&s2);
SLInsert(&s2, 2, 40);
SLPrint(&s2);
SLErase(&s2, 3);
SLPrint(&s2);
}
void menu()
{
//printf("1、尾插数据");
}
int main()
{
//Test1();
//数组越界读一般都不会报错,越界写一般会报错
//越界写一般是在数组后边的位置写一些标志位的数据,主要是检查该位置数据有没有被改写
Test2();
return 0;
}
代码整体思路并不难,主要是要注意一点,那就是针对于一些特殊情况的处理,否则很有可能出现数组越界的问题,这个大家可以看看代码,再自己手搓一份出来。
链表不多说,先上一个单链表的结构
typedef struct SListNode
{
int value;
struct SListNode* next;
}SLNode;
下面来拆解一下,我们可以看到,这是一个链表节点的结构体,定义的是什么呢,首先我们定义了节点的值,其次我们定义一个指针指向下一个节点,就像是火车一样一节连着一节,形成链式结构,所以也就是链表。
那么有指向下一个节点的指针,可以不可以有指向前一个节点的指针呢,答案是有,那就是双向链表,上结构!
typedef struct DListNode
{
int val;
struct DListNode* pre;
struct DListNode* next;
}DLNode;
由此我们就可以很清楚链表的两种基本的结构也就是单链表和双链表。那么在引入一个概念,带头结点,头节点这个概念可以这样理解,例如一个火车,有效数据是乘客,那么乘客只能乘坐在火车头以后的载客车厢,火车头只是一个标识这个火车的作用,头节点就相当于是火车头,本身不存储数据但是可以引领整个链表。ps:注意并不是每个链表都一定需要加上头节点,只是在某些场景下加入头节点会减少一些不必要的麻烦。
单链表的一些操作
void SLTPrint(SLNode* phead);
//打印链表,phead是一个结构体指针指向链表的第一个元素,存储的就是第一个元素的地址
void SLTPushBack(SLNode** pphead, SLNDateType x);
//链表的尾插
//1、没有节点的情况
//2、有节点的情况
//所以无论是第一种还是第二种,统一使用二级指针就可以
void SLTPushFront(SLNode** pphead, SLNDateType x);
//链表的头插
//因为需要改变最开始指向链表的地址,所以也需要二级指针
void SLTPopBack(SLNode** pphead);
//链表的尾删
//一前一后的两个指针进行,记录位置
void SLTPopFront(SLNode** pphead);
//链表的头删
SLNode* SLTFind(SLNode* phead, SLNDateType x);
//链表的查找
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDateType x);
//链表的任意位置插入
//插入是指在pos的前面的位置进行插入
void SLTErase(SLNode** pphead, SLNode* pos);
//链表的任意位置删除
//删除也是删除
void SLTDestroy(SLNode** pphead);
//链表的销毁
void SLTPrint(SLNode* phead)
{
SLNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->value);
cur = cur->next;
}
printf("NULL\n");
}
SLNode* CreateNode(SLNDateType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->value = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLNode** pphead, SLNDateType x)
{
assert(pphead);
SLNode* newnode = CreateNode(x);
if (*pphead == NULL)
{
//链表为空的时候
*pphead = newnode;
//改变的是结构体的指针,所以使用二级指针
}
else
{
//链表不为空的时候,找尾
SLNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
//改变的是结构体的内容
}
}
void SLTPushFront(SLNode** pphead, SLNDateType x)
{
assert(pphead);
SLNode* newnode = CreateNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopBack(SLNode** pphead)
{
assert(pphead);
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLNode* pre = NULL;
SLNode* tail = *pphead;
while (tail->next != NULL)
{
pre = tail;
tail = tail->next;
}
pre->next = tail->next;
free(tail);
}
}
void SLTPopFront(SLNode** pphead)
{
assert(*pphead);
SLNode* cur = *pphead;
*pphead = cur->next;
free(cur);
}
SLNode* SLTFind(SLNode* phead, SLNDateType x)
{
SLNode* cur = phead;
while (cur)
{
if (cur->value == x)
{
break;
}
cur = cur->next;
}
return cur;
}
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDateType x)
{
assert(pphead);
assert(*pphead);//两者都为空,或者两者都不是空
assert(pos);
//SLNode* newnode = CreateNode(x);
//SLNode* pre = NULL;
//SLNode* cur = *pphead;
//如果是在第一个节点进行插入,相当于是头插法(无论链表是只有一个还是有多个节点)
//while (cur->value != pos->value)
//{
// pre = cur;
// cur = cur->next;
//}
//if (pre == NULL)
//{
// newnode->next = cur;
// *pphead = newnode;
//}
//else
//{
// newnode->next = cur;
// pre->next = newnode;
//}
if (*pphead == pos)
{
SLTPushFront(pphead, x);
}
else
{
SLNode* pre = *pphead;
while (pre->value != pos->value)
{
pre = pre->next;
}
SLNode* newnode = CreateNode(x);
pre->next = newnode;
newnode->next = pos;
}
}
void SLTErase(SLNode** pphead, SLNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
SLNode* pre = NULL;
SLNode* cur = *pphead;
while (cur->value != pos->value)
{
pre = cur;
cur = cur->next;
}
if (pre == NULL)
{
*pphead = cur->next;
free(cur);
}
else
{
pre->next = cur->next;
free(cur);
}
}
void SLTDestroy(SLNode** pphead)
{
assert(pphead);
//assert(*pphead);
SLNode* cur = *pphead;
while (*pphead)
{
*pphead = cur->next;
free(cur);
cur = *pphead;
}
}
void Test1()
{
SLNode* L1 = NULL;
SLTPushBack(&L1, 1);
SLTPushBack(&L1, 2);
SLTPrint(L1);
SLTPopBack(&L1);
SLTPrint(L1);
}
void Test2()
{
SLNode* L1 = NULL;
SLTPushFront(&L1, 1);
SLTPushFront(&L1, 2);
SLTPrint(L1);
SLTPopFront(&L1);
SLTPrint(L1);
SLTPopFront(&L1);
SLTPrint(L1);
}
void Test3()
{
SLNode* L1 = NULL;
SLTPushBack(&L1, 1);
SLTPushBack(&L1, 2);
SLTPushBack(&L1, 3);
SLTPushBack(&L1, 4);
SLTPrint(L1);
SLNode* pos = SLTFind(L1, 1);
SLTInsert(&L1, pos, 11);
SLTPrint(L1);
//SLTErase(&L1, pos);
//SLTPrint(L1);
}
void Test4()
{
SLNode* L1 = NULL;
SLTPushBack(&L1, 1);
SLTPushBack(&L1, 2);
SLTPushBack(&L1, 3);
SLTPushBack(&L1, 4);
SLTPrint(L1);
SLTDestroy(&L1);
SLTPrint(L1);
}
int main()
{
//Test1();
//Test2();
//Test3();
Test4();
return 0;
}
双链表我实现的是一个带头结点双向循环链表,循环其实不难理解就是第一个节点指向最后一个节点,从而形成一个环这样的方式。
DLNode* InitDL();//初始化双向循环链表
void DLPrint(DLNode* phead);//打印双向循环链表
DLNode* DLCreateNode(DLTDataType x);//创建头节点
void DLPushBack(DLNode* phead, DLTDataType x);//尾插
void DLPopBack(DLNode* phead);//尾删
void DLPushFront(DLNode* phead, DLTDataType x);//头插
void DLPopFront(DLNode* phead);//头删
DLNode* DLFind(DLNode* phead, DLTDataType x);//查找
void DLInsert(DLNode* pos, DLTDataType x);//双向链表pos插入(前插为例)
void DLErase(DLNode* pos);//双向链表pos删除
void DLDestroy(DLNode* phead);//链表的销毁
DLNode* InitDL()
{
DLNode* phead = DLCreateNode(-1);
phead->pre = phead;
phead->next = phead;
return phead;
}
void DLPrint(DLNode* phead)
{
assert(phead);
printf("哨兵<=>");
DLNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
printf("哨兵\n");
}
DLNode* DLCreateNode(DLTDataType x)
{
DLNode* newnode = (DLNode*)malloc(sizeof(DLNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->pre = newnode;
newnode->next = newnode;
return newnode;
}
void DLPushBack(DLNode* phead, DLTDataType x)
{
assert(phead);
DLNode* tail = phead->pre;
DLNode* newnode = DLCreateNode(x);
//尾插
newnode->next = phead;
tail->next = newnode;
newnode->pre = tail;
phead->pre = newnode;
}
void DLPopBack(DLNode* phead)
{
assert(phead);
//不可以删除哨兵
assert(phead->next != phead);
DLNode* tail = phead->pre;
phead->pre = tail->pre;
tail->pre->next = phead;
free(tail);
tail = NULL;
}
void DLPushFront(DLNode* phead, DLTDataType x)
{
assert(phead);
DLNode* newnode = DLCreateNode(x);
newnode->next = phead->next;
phead->next->pre = newnode;
newnode->pre = phead;
phead->next = newnode;
}
void DLPopFront(DLNode* phead)
{
assert(phead);
assert(phead->next != phead);
DLNode* cur = phead->next;
phead->next = cur->next;
cur->next->pre = phead;
free(cur);
cur = NULL;
}
DLNode* DLFind(DLNode* phead, DLTDataType x)
{
assert(phead);
DLNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void DLInsert(DLNode* pos, DLTDataType x)
{
assert(pos);
//如果pos位置强行传入哨兵节点,那么在pos之前插入就相当于是尾插
DLNode* pospre = pos->pre;
DLNode* newnode = DLCreateNode(x);
pospre->next = newnode;
newnode->pre = pospre;
newnode->next = pos;
pos->pre = newnode;
}
void DLErase(DLNode* pos)
{
assert(pos);
DLNode* pospre = pos->pre;
DLNode* posnext = pos->next;
pospre->next = posnext;
posnext->pre = pospre;
free(pos);
pos = NULL;
}
void DLDestroy(DLNode* phead)
{
assert(phead);
DLNode* cur = phead->next;
while (cur != phead)
{
DLNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
printf("销毁完成!\n");
}
void Test1()
{
DLNode* DL1 = InitDL();
DLPushBack(DL1, 1);
DLPushBack(DL1, 2);
DLPushBack(DL1, 3);
DLPrint(DL1);
DLPopBack(DL1);
DLPrint(DL1);
}
void Test2()
{
DLNode* DL2 = InitDL();
DLPushBack(DL2, 1);
DLPushBack(DL2, 2);
DLPushBack(DL2, 3);
DLPrint(DL2);
DLPushFront(DL2, 4);
DLPrint(DL2);
DLPopFront(DL2);
DLPrint(DL2);
}
void Test3()
{
DLNode* DL3 = InitDL();
DLPushBack(DL3, 1);
DLPushBack(DL3, 2);
DLPushBack(DL3, 3);
DLPrint(DL3);
DLNode* pos = DLFind(DL3, 3);
DLInsert(pos, 55);
DLPrint(DL3);
DLErase(pos);
DLPrint(DL3);
pos = NULL;//传递是形参,所以为了不出现野指针,只能在此处置空
DLDestroy(DL3);
DL3 = NULL;//传递是形参,所以为了不出现野指针,只能在此处置空
}
int main()
{
//Test1();
//Test2();
Test3();
return 0;
}