完整代码在码云仓库:
C++数据结构: 本仓库存放C++数据结构代码https://gitee.com/j-jun-jie/c-data-structure.git
1.顺序表与链表的优劣
- 顺序表
优点:1物理空间连续,2.可以实现下标随机访问。
缺点:1.空间不够要扩容,所以有一定空间浪费和空间消耗。
2.头部或中间位置插入删除效率低。
改善方案:1.按需申请空间,需要一个空间开辟一个。
2.头部,中间插入删除不挪动数据。
——————链表
- 链表
#pragma once #include<stdio.h> #include<assert.h> #include<string.h> #include<stdlib.h> typedef int SLTDateType; struct SListNode { SLTDateType date; //数据个数 struct SListNode* next; //指向下一个结点的指针 };
这是在头文件区域定义的,上面的#pragma是防止头文件重复的,下面的结构体是链表的主体。
先写四个节点
#define _CRT_SECURE_NO_WARNINGS 1 #include "SList.h" void TestSList1() { SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode)); assert(n1); SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode)); assert(n2); SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode)); assert(n3); SLTNode* n4 = (SLTNode*)malloc(sizeof(SLTNode)); assert(n4); n1->date = 1; n2->date = 2; n3->date = 3; n4->date = 4; n1->next = n2; n2->next = n3; n3->next = n4; n4->next = NULL; } int main() { TestSList1(); return 0; }
next中存的是下一个数据的地址,最后一个数据的next中存的是NULL。
//打印 void SListPrint(SLTNode* phead) { //phead存首元素地址 SLTNode* cur = phead; while (cur != NULL) { printf("%d->", cur->date); cur = cur->next; //指向下一个 } printf("NULL\n"); }
为了方便,先写一个打印函数。
2.单链表的打印
//打印
void SListPrint(SLTNode* phead)
{
//phead存首元素地址
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->date);
cur = cur->next; //指向下一个
}
printf("NULL\n");
}
phead中存的是首元素地址,要不然一会打印不能从头开始,所以又创建个新变量cur。
中间这个->是咱自己在printf中加的。
3.尾插实现
//尾插 void SListPushBack(SLTNode* phead, SLTDateType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); newnode->date = x; newnode->next = NULL; //找节点 SLTNode* tail = phead; while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; }
思路:新开辟一个空间,这个空间分两部分,一部分用来存值x,一部分用来存地址,但是在最后所以存NULL。接下来我要去空间中去找最后的地址,所以又创建个tail。
那如果是空指针实现尾插呢?
很明显,程序直接崩掉了,所以尾插代码还不完善。
//尾插 void SListPushBack(SLTNode* phead, SLTDateType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); assert(newnode); newnode->date = x; newnode->next = NULL; //先判断phead是否为空 if (phead == NULL) { phead = newnode; } else { //找节点 SLTNode* tail = phead; while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; } }
但是打印结果是NULL(不在粘贴图了),为啥呢?
3.2二级指针
形参是实参的一份临时拷贝,要改变形参,就要传实参的地址,同类,我要改变指针,那我就要传指针的地址,指针是一份地址,指针的地址又是一份地址,所以用到二级指针。
咱们要做的是把NULL改成要插入元素的地址,所以用到二级指针,而不仅仅改变数值。
//尾插 void SListPushBack(SLTNode** pphead, SLTDateType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); assert(newnode); newnode->date = x; newnode->next = NULL; //先判断phead是否为空 if (*pphead == NULL) { *pphead = newnode; //*pphead代表的是地址,而不是解引用 } else { //找节点 SLTNode* tail = *pphead; while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; } }
print函数可以不传,因为它不需要改变数值。
4.头插
4.1节点函数
每次插入都要开辟节点,那不妨创建一个节点函数
//开辟空间 SLTNode* BuySListNode(SLTDateType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); assert(newnode); newnode->date = x; newnode->next = NULL; return newnode; }
4.2头插
好好理解一下兄弟们。
5.头删
//头删 void SListPopFront(SLTNode** pphead) { SLTNode* next = (*pphead)->next; free(*pphead); *pphead = next; }
这个很好理解,我先找一个寄存器存下一个数的地址,然后free掉,我再把寄存器中的地址传给*pphead然后通过地址直接访问的是原来的一串数的第二个,因为我找的地址就是第二个数的,所以第一个数就被free了。
尾删两次。注意一定要判空。
6.尾删
//尾删 void SListPopBack(SLTNode** pphead) { SLTNode* tail = *pphead; SLTNode* tailPrve = NULL; while (tail->next != NULL)//前提是tail不是空 { tailPrve = tail; tail = tail->next; } free(tail); tailPrve->next = NULL; }
当tail访问最后一个是,就不进入while循环了,所以tailPrve是倒数第二个值。
7.某位置插入/删除
//某处添加 void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x) { assert(pos && *pphead!=NULL); if (*pphead == pos) { SListPushFront(pphead, x); } else { SLTNode* prev = *pphead; SLTNode* newnode = BuySListNode(x); //开辟节点 while (prev->next != pos); { prev = prev->next; } prev->next = newnode; //把newnode接在prev上,再把pos和newnode接在一起 newnode->next = pos; } }
- 在某位置删除
//某处删除 void SListErase(SLTNode** pphead, SLTNode* pos) { assert(pos && pphead); if (*pphead == pos) { SListPopFront(pphead); } else { SLTNode* prev = *pphead; while (prev->next != NULL) { prev = prev->next; } prev->next = pos->next; free(pos); } }
9.在pos后面添加和删除
//pos后添加 void SListInsertafter(SLTNode** pphead, SLTNode* pos, SLTDateType x) { assert(pos); SLTNode* newnode = BuySListNode(x); //开辟节点 SLTNode* next = pos->next; pos->next = newnode; newnode->next = next; //不用考虑顺序 } //pos后删除 void SListEraseafter(SLTNode** pphead, SLTNode* pos) { assert(pos); if (pos->next == NULL) //最后了就不需要删除了 return; SLTNode* del = pos->next; //正好把中间那个隔离 pos->next = del->next; free(del); del = NULL; }
由于时间有限,最后几个就不在解释了,有不太理解的兄弟们私信我。