目录
后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!
详细实现链接:
<单链表(含头结点)>《数据结构(C语言版)》
1.链表表示和实现(单链表+双向链表)
顺序表的问题及思考问题:1. 中间/头部的插入删除,时间复杂度为O(N)2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。思考:如何解决以上问题呢?下面给出了链表的结构来看看。
1.1 链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。举例(这里采用单链表画图举例):理解cur=cur->next:
理解tail !=NULL:
理解tail->next !=NULL:
对比发现两者最终结束的位置不同!
实际中要实现的链表的结构非常多样,以下情况组合起来就有8种链表结构:1. 单向、双向2. 带头、不带头3. 循环、非循环虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。1.2单链表的实现
SList.h:
#pragma once #include <stdio.h> #include <stdlib.h> typedef int SLTDataType; struct SListNode { SLTDataType data; struct SListNode* next; }; typedef struct SListNode SLTNode; // 不用改变链表的头指针,传一级指针 //单链表打印 void SListPrint(SLTNode* phead); // 可能会要改变链表的头指针,传二级指针 //单链表尾插 void SListPushBack(SLTNode** pphead, SLTDataType x); //单链表头插 void SListPushFront(SLTNode** pphead, SLTDataType x); //单链表头删 void SListPopFront(SLTNode** pphead); //单链表尾删 void SListPopBack(SLTNode** pphead); //单链表按值查找 SLTNode* SListFind(SLTNode* phead, SLTDataType x); // 在pos的前面插入x void SListInsert(SLTNode** phead, SLTNode* pos, SLTDataType x); // 删除pos位置的值 void SListErase(SLTNode** phead, SLTNode* pos); // 有些地方也有这样的 在pos的前面插入x //void SListInsert(SLTNode** phead, int i, SLTDataType x); 删除pos位置的值 //void SListErase(SLTNode** phead, int i);
SList.c:
#include "SList.h" //单链表打印 void SListPrint(SLTNode* phead) { SLTNode* cur = phead; while (cur != NULL) { printf("%d->", cur->data); cur = cur->next; //通过"物理结构"更好理解,地址赋值,使得cur进行了"移动" } printf("NULL\n"); } //封装函数:动态开辟新结点 SLTNode* BuySListNode(SLTDataType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); newnode->data = x; newnode->next = NULL; return newnode; } //单链表尾插 void SListPushBack(SLTNode** pphead, SLTDataType x) { SLTNode* newnode = BuySListNode(x); if (*pphead == NULL) { *pphead = newnode; } else { // 找尾节点的指针 SLTNode* tail = *pphead; while (tail->next != NULL) { tail = tail->next; } // 尾节点,链接新节点 tail->next = newnode; } } //单链表头插 void SListPushFront(SLTNode** pphead, SLTDataType x) { SLTNode* newnode = BuySListNode(x); newnode->next = *pphead; *pphead = newnode; } //单链表头删 void SListPopFront(SLTNode** pphead) { SLTNode* next = (*pphead)->next; free(*pphead); *pphead = next; } //单链表尾删 void SListPopBack(SLTNode** pphead) { // 1、空 // 2、一个节点 // 3、一个以上的节点 if (*pphead == NULL) { return; } else if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } else { SLTNode* prev = NULL; SLTNode* tail = *pphead; while (tail->next != NULL) { prev = tail; tail = tail->next; } free(tail); prev->next = NULL; } } //单链表按值查找 SLTNode* SListFind(SLTNode* phead, SLTDataType x) { SLTNode* cur = phead; //while (cur != NULL) while (cur) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; } // 在pos的前面插入x void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) { if (pos == *pphead) { SListPushFront(pphead, x); } else { SLTNode* newnode = BuySListNode(x); SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = newnode; newnode->next = pos; } } // 删除pos位置的值 void SListErase(SLTNode** pphead, SLTNode* pos) { if (pos == *pphead) { SListPopFront(pphead); } else { SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); } }
Test.c:
#include "SList.h" void TestSList1() { SLTNode* plist = NULL; SListPushBack(&plist, 1); SListPushBack(&plist, 2); SListPushBack(&plist, 3); SListPushBack(&plist, 4); SListPrint(plist); SListPushFront(&plist, 0); SListPrint(plist); SListPopFront(&plist); SListPopFront(&plist); SListPopFront(&plist); SListPrint(plist); SListPopFront(&plist); SListPopFront(&plist); SListPrint(plist); } void TestSList2() { SLTNode* plist = NULL; SListPushBack(&plist, 1); SListPushBack(&plist, 2); SListPushBack(&plist, 3); SListPushBack(&plist, 4); SListPrint(plist); SListPopBack(&plist); SListPopBack(&plist); SListPopBack(&plist); SListPopBack(&plist); SListPopBack(&plist); SListPrint(plist); } void TestSList3() { SLTNode* plist = NULL; SListPushBack(&plist, 1); SListPushBack(&plist, 2); SListPushBack(&plist, 3); SListPushBack(&plist, 4); SListPrint(plist); //在3的前面插入一个30 SLTNode* pos = SListFind(plist, 3); if (pos) { SListInsert(&plist, pos, 30); } SListPrint(plist); //在1的前面插入10 pos = SListFind(plist, 1); if (pos) { SListInsert(&plist, pos, 10); } SListPrint(plist); } void TestSList4() { SLTNode* plist = NULL; SListPushBack(&plist, 1); SListPushBack(&plist, 2); SListPushBack(&plist, 3); SListPushBack(&plist, 4); SListPrint(plist); SLTNode* pos = SListFind(plist, 1); if (pos) { SListErase(&plist, pos); } SListPrint(plist); pos = SListFind(plist, 4); if (pos) { SListErase(&plist, pos); } SListPrint(plist); pos = SListFind(plist, 3); if (pos) { SListErase(&plist, pos); } SListPrint(plist); pos = SListFind(plist, 2); if (pos) { SListErase(&plist, pos); } SListPrint(plist); } int main() { TestSList1(); TestSList2(); TestSList3(); TestSList4(); return 0; }
1.3 双向链表的表示和实现
2.顺序表和链表的区别和联系
顺序表:优点:空间连续、支持随机访问缺点:1. 中间或前面部分的插入删除时间复杂度O(N)2. 2.增容的代价比较大。链表:缺点:以节点为单位存储,不支持随机访问优点:1. 任意位置插入删除时间复杂度为O(1)2. 没有增容消耗,按需申请节点空间,不用了直接释放。