一、链表的概念
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
链表由两个部分组成:数据域和指针域,数据域用来存放数据,指针域用来链接到下一个数据,具体看下图;
从图中可以看出,每个数据的下面的指针域里都存储了下一个数据的地址,那么我们通过访问第一个数据的地址,顺藤摸瓜就能访问到其他数据
二、单链表的接口实现
1.首先创建一个链表的结构
typedef int SLDataType;//这里typedef可以用于存储类型之间的切换 typedef struct SListNode { SLDataType Data;//数据域 struct SListNode* next;//指针域 }SLNode;
2.单链表的结点申请
//动态申请一个结点 SLNode* BuyListNode(SLDataType x) { SLNode* newnode = (SLNode*)malloc(sizeof(SLNode)); if (newnode == NULL) { printf("malloc fail\n"); exit(-1); } newnode->Data = x; //x的值存放在newnode数据域中 newnode->next = NULL; //newnode的指针域置空 return newnode; }
3.单链表的打印
//单链表的打印 void SListPrint(SLNode* phead) { assert(phead); SLNode* cur = phead; while (cur != NULL) { printf("%d->", cur->Data); //打印链表数据 cur = cur->next; //让cur指向下一个数据 } //当cur为空时,链表打印结束 printf("NULL\n\n"); }
4.单链表的尾插
//单链表的尾插 void SListPushBack(SLNode** pphead, SLDataType x) { assert(pphead); SLNode* newnode = BuyListNode(x);//插入数据需要申请结点 if (*pphead == NULL) { *pphead = newnode; } else { //尾插--就去找链表的尾巴 SLNode* tail = *pphead; while (tail->next != NULL) { tail = tail->next; } //找到尾巴,把尾结点的指针域指向插入的新结点的地址 tail->next = newnode; } }
5.单链表的头插
//单链表的头插 void SListPushFront(SLNode** pphead, SLDataType x) { assert(pphead); SLNode* newnode = BuyListNode(x); newnode->next = *pphead; //让newnode的指针域指向链表的数据域(就是链表的头) *pphead = newnode; //让newnode作新链表的头 }
6.单链表的尾删
//单链表的尾删 void SListPopBack(SLNode** pphead) { assert(*pphead); //如果链表为空 if (*pphead == NULL) { free(*pphead); *pphead = NULL; } else { //先要找到尾结点(tail),在记录一下尾结点的前一个结点(prev); SLNode* tail = *pphead; SLNode* prev = NULL; while (tail->next != NULL) { prev = tail; tail = tail->next; } //找到尾结点了,释放它并置空; free(tail); tail = NULL; //此时尾结点的前一个结点(prev)还保存着tail结点的数据的地址,要让它置空; prev->next = NULL; } }
7.单链表的头删
//单链表的头删 void SListPopFront(SLNode** pphead) { assert(*pphead); SLNode* next = (*pphead)->next;//记录头结点的下一个结点的地址 free(*pphead);//释放头结点 *pphead = next;//头结点的下一个结点作新的头 }
8.单链表的查找
//单链表的查找 SLNode* SListFind(SLNode* phead, SLDataType x) { assert(phead); SLNode* cur = phead; while (cur != NULL) { if (cur->Data == x) { return cur; } else { cur = cur->next; } } return NULL; }
9.单链表在pos位置前插入一个结点
//单链表在pos位置前插入一个结点 void SListInsert(SLNode** pphead, SLNode* pos, SLDataType x) { assert(pphead); assert(pos); SLNode* newnode = BuyListNode(x);//申请结点 //如果只有一个结点 if (*pphead == pos) { newnode->next = *pphead; *pphead = newnode; } else { //找到pos位置的前一个结点,并记录 SLNode* posprev = *pphead; while (posprev->next != pos) { posprev = posprev->next; } //让新结点的指针域指向pos,再让pos的前一个结点的指针域重新指向新结点的数据域地址,就很好的插入到pos的前面了 newnode->next = pos; posprev->next = newnode; } }
10.单链表在pos位置后插入一个结点
//单链表在pos位置后插入一个节点 void SListInsertAfter(SLNode* pos, SLDataType x) { assert(pos); SLNode* newnode = BuyListNode(x);//申请结点 newnode->next = pos->next;//让新结点的指针域指向pos位置的下一个结点的数据域地址 pos->next = newnode;//pos的指针域指向新结点的数据域地址 }
11.单链表删除pos位置的结点
//单链表删除pos位置的节点 void SListErase(SLNode** pphead, SLNode* pos) { assert(*pphead); //如果链表只有一个结点 if (*pphead == pos) { SListPopBack(pphead); } else { //找到pos位置的前一个位置(posPrev),并记录 SLNode* posPrev = *pphead; while (posPrev->next != pos) { posPrev = posPrev->next; } //让pos位置的前一个结点posPrev的指针域指向pos结点的指针域,如果pos位置还有结点,即指向了pos位置的下一个结点的数据域的地址; posPrev->next = pos->next; free(pos);//释放pos } }
12.单链表删除pos位置后的结点
//单链表删除pos位置后的节点 void SListEraseAfter(SLNode* pos) { assert(pos); assert(pos->next); SLNode* next = pos->next; pos->next = next->next; free(next); }
13.单链表的销毁
单链表的销毁并不能和顺序表一样直接释放,顺序表是开辟的连续的空间,链表不是;
要将链表释放需要释放每一个结点;
可以采用双指针;
//单链表的销毁 void SListDestroy(SLNode** pphead) { assert(*pphead); SLNode* cur = *pphead; while (cur != NULL) { SLNode* next = cur->next; free(cur); cur = next; } *pphead = NULL; }
三、源文件
SList.h
#include <stdio.h> #include <stdlib.h> #include <assert.h> typedef int SLDataType; typedef struct SListNode { SLDataType Data; struct SListNode* next; }SLNode; //动态申请一个节点 SLNode* BuyListNode(SLDataType x); //单链表的打印 void SListPrint(SLNode* phead); //单链表的尾插 void SListPushBack(SLNode** pphead, SLDataType x); //单链表的头插 void SListPushFront(SLNode** pphead, SLDataType x); //单链表的尾删 void SListPopBack(SLNode** pphead); //单链表的头删 void SListPopFront(SLNode** pphead); //单链表的查找 SLNode* SListFind(SLNode* phead, SLDataType x); //单链表在pos位置前插入一个节点 void SListInsert(SLNode** pphead, SLNode* pos, SLDataType x); //单链表在pos位置后插入一个节点 void SListInsertAfter(SLNode* pos, SLDataType x); //单链表删除pos位置的节点 void SListErase(SLNode** pphead, SLNode* pos); //单链表删除pos位置后的节点 void SListEraseAfter(SLNode* pos); //单链表的销毁 void SListDestroy(SLNode** pphead);
SList.c
#include "SList.h" //动态申请一个结点 SLNode* BuyListNode(SLDataType x) { SLNode* newnode = (SLNode*)malloc(sizeof(SLNode)); if (newnode == NULL) { printf("malloc fail\n"); exit(-1); } newnode->Data = x; //x的值存放在newnode数据域中 newnode->next = NULL; //newnode的指针域置空 return newnode; } //单链表的打印 void SListPrint(SLNode* phead) { assert(phead); SLNode* cur = phead; while (cur != NULL) { printf("%d->", cur->Data); //打印链表数据 cur = cur->next; //让cur指向下一个数据 } //当cur为空时,链表打印结束 printf("NULL\n\n"); } //单链表的尾插 void SListPushBack(SLNode** pphead, SLDataType x) { assert(pphead); SLNode* newnode = BuyListNode(x);//插入数据需要申请结点 if (*pphead == NULL) { *pphead = newnode; } else { //尾插--就去找链表的尾巴 SLNode* tail = *pphead; while (tail->next != NULL) { tail = tail->next; } //找到尾巴,把尾结点的指针域指向插入的新结点的地址 tail->next = newnode; } } //单链表的头插 void SListPushFront(SLNode** pphead, SLDataType x) { assert(pphead); SLNode* newnode = BuyListNode(x); newnode->next = *pphead; //让newnode的指针域指向链表的数据域(就是链表的头) *pphead = newnode; //让newnode作新链表的头 } //单链表的尾删 void SListPopBack(SLNode** pphead) { assert(*pphead); //如果链表为空 if (*pphead == NULL) { free(*pphead); *pphead = NULL; } else { //先要找到尾结点(tail),在记录一下尾结点的前一个结点(prev); SLNode* tail = *pphead; SLNode* prev = NULL; while (tail->next != NULL) { prev = tail; tail = tail->next; } //找到尾结点了,释放它并置空; free(tail); tail = NULL; //此时尾结点的前一个结点(prev)还保存着tail结点的数据的地址,要让它置空; prev->next = NULL; } } //单链表的头删 void SListPopFront(SLNode** pphead) { assert(*pphead); SLNode* next = (*pphead)->next;//记录头结点的下一个结点的地址 free(*pphead);//释放头结点 *pphead = next;//头结点的下一个结点作新的头 } //单链表的查找 SLNode* SListFind(SLNode* phead, SLDataType x) { assert(phead); SLNode* cur = phead; while (cur != NULL) { if (cur->Data == x) { return cur; } else { cur = cur->next; } } return NULL; } //单链表在pos位置前插入一个结点 void SListInsert(SLNode** pphead, SLNode* pos, SLDataType x) { assert(pphead); assert(pos); SLNode* newnode = BuyListNode(x);//申请结点 //如果只有一个结点 if (*pphead == pos) { newnode->next = *pphead; *pphead = newnode; } else { //找到pos位置的前一个结点,并记录 SLNode* posprev = *pphead; while (posprev->next != pos) { posprev = posprev->next; } //让新结点的指针域指向pos,再让pos的前一个结点的指针域重新指向新结点的数据域地址,就很好的插入到pos的前面了 newnode->next = pos; posprev->next = newnode; } } //单链表在pos位置后插入一个节点 void SListInsertAfter(SLNode* pos, SLDataType x) { assert(pos); SLNode* newnode = BuyListNode(x);//申请结点 newnode->next = pos->next;//让新结点的指针域指向pos位置的下一个结点的数据域地址 pos->next = newnode;//pos的指针域指向新结点的数据域地址 } //单链表删除pos位置的节点 void SListErase(SLNode** pphead, SLNode* pos) { assert(*pphead); //如果链表只有一个结点 if (*pphead == pos) { SListPopBack(pphead); } else { //找到pos位置的前一个位置(posPrev),并记录 SLNode* posPrev = *pphead; while (posPrev->next != pos) { posPrev = posPrev->next; } //让pos位置的前一个结点posPrev的指针域指向pos结点的指针域,如果pos位置还有结点,即指向了pos位置的下一个结点的数据域的地址; posPrev->next = pos->next; free(pos);//释放pos } } //单链表删除pos位置后的节点 void SListEraseAfter(SLNode* pos) { assert(pos); assert(pos->next); SLNode* next = pos->next; pos->next = next->next; free(next); } //单链表的销毁 void SListDestroy(SLNode** pphead) { assert(*pphead); SLNode* cur = *pphead; while (cur != NULL) { SLNode* next = cur->next; free(cur); cur = next; } *pphead = NULL; }
Test.c
#include "SList.h" void TestList1() { SLTNode* plist = NULL; SListPushBack(&plist, 1); SListPushBack(&plist, 2); SListPushBack(&plist, 3); SListPushBack(&plist, 4); SListPushBack(&plist, 5); SListPrint(plist); //SListPushFront(&plist, 1); //SListPushFront(&plist, 2); //SListPushFront(&plist, 3); //SListPushFront(&plist, 4); //SListPushFront(&plist, 5); //SListPrint(plist); } void TestList2() { SLTNode* plist = NULL; SListPushFront(&plist, 1); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPushFront(&plist, 4); SListPushFront(&plist, 5); SListPopFront(&plist); SListPopFront(&plist); SListPrint(plist); } void TestList3() { SLTNode* plist = NULL; SListPushFront(&plist, 1); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPushFront(&plist, 2); SListPushFront(&plist, 5); SListPrint(plist); SLTNode* pos = SListFind(plist, 2); int i = 1; while (pos) { printf("%d个pos节点:%p->%d\n", i++, pos, pos->data); pos = SListFind(pos->next, 2); } pos = SListFind(plist, 3); if (pos) { pos->data = 30; } SListPrint(plist); } void TestList4() { SLTNode* plist = NULL; SListPushFront(&plist, 1); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPushFront(&plist, 2); SListPushFront(&plist, 4); SListPushFront(&plist, 5); SListPrint(plist); //SLTNode* pos = SListFind(plist, 5); //if (pos) //{ // SListInsert(&plist, pos, 30); //} //SListPrint(plist); } void TestList5() { SLTNode* plist = NULL; SListPushFront(&plist, 1); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPushFront(&plist, 2); SListPushFront(&plist, 4); SListPushFront(&plist, 5); SListPrint(plist); /* SLTNode* pos = SListFind(plist, 5); SListInsertAfter(pos, 20); SListPrint(plist); */ /* SLTNode* pos = SListFind(plist, 3); SListEase(&plist, pos); SListPrint(plist); */ SLTNode* pos = SListFind(plist, 5); SListEaseAfter(pos); SListPrint(plist); SListDestory(&plist); } int main() { TestList1(); //TestList2(); //TestList3(); //TestList4(); //TestList5(); return 0; }
下一期更新顺序表和链表相关的LeetCode题