1.链表
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
2.链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
①单向或者双向
②带头或者不带头
③循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
- 无头单向非循环链表
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
小记tip:
全局变量或者静态存储都在数据段上,而在函数内部临时开辟的则存储在栈区(记忆技巧:栈区后来先出,生命周期短,适合存储临时使用的变量,在函数执行结束时会自动被释放),堆区存放的则是在函数运行过程中开辟出来的空间,需要程序员进行手动分区以及释放,也可能程序结束时由OS回收,所以也可能会发生内存泄漏问题
3.接口实现
<SList.h>
#pragma oncetypedef int SLNDataType;// Single List
typedef struct SListNode
{SLNDataType val;struct SListNode* next;
}SLNode;void SLTPrint(SLNode* phead); // 遍历打印
void SLTPushBack(SLNode** pphead, SLNDataType x); // 尾插
void SLTPushFront(SLNode** pphead, SLNDataType x); // 头插
SLNode* CreatNode(SLNDataType);// 新节点的创建
SLNode* SLTFind(SLNode* phead, SLNDataType x); // 查找void SLTPopBack(SLNode** pphead); // 尾删
void SLTPopFront(SLNode** pphead); // 头删void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x); //在pos的前面插入
void SLTErase(SLNode** pphead, SLNode* pos); //删除pos位置void SLTInsertAfter(SLNode* pos, SLNDataType x); //在后面插入
void SLTEraseAfter(SLNode* pos); //在后面删除void SLTDestroy(SLNode** pphead); //销毁整个单链表
4.函数实现
<SList.c>
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"SList.h"
#include<stdlib.h>
#include<assert.h>void SLTPrint(SLNode* phead)
{SLNode* cur = phead;while (cur != NULL){printf("%d-> ", cur->val);cur = cur->next;}printf("NULL\n");
}SLNode* CreatNode(SLNDataType x) // 新节点的创建
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}//尾部插入
void SLTPushBack(SLNode** pphead, SLNDataType x)//pphead是plist的拷贝,改变pphead不能改变plist
{assert(pphead);SLNode* newnode = CreatNode(x);if (*pphead == NULL) // 链表为空{*pphead = newnode;}else {//找尾,最后一个节点SLNode* tail = *pphead;while (tail->next != NULL)//tail->next原因:没有把链表和新节点连接起来,不指向next的话会发生内存泄漏,出了作用域tail销毁{tail = tail->next;}tail->next = newnode;}
}//头部插入
void SLTPushFront(SLNode** pphead, SLNDataType x)
{assert(pphead);SLNode* newnode = CreatNode(x);newnode->next = *pphead;*pphead = newnode;
}// 尾删
void SLTPopBack(SLNode** pphead)
{//如果删完了if (*pphead == NULL)return;//或者用断言//assert(*pphead);//if --- 1个节点//else --- 多个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//找尾,最后一个节点SLNode* prev = NULL;SLNode* tail = *pphead;while (tail->next != NULL)//tail->next原因:没有把链表和新节点连接起来,不指向next的话会发生内存泄漏,出了作用域tail销毁{prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;}
}// 头删
void SLTPopFront(SLNode** pphead)
{//空if (*pphead == NULL)return;//或者用断言//assert(*pphead);// 第一种写法SLNode* tail = *pphead;*pphead = tail->next;free(tail);// 第二种写法/*SLNode* tmp = (*pphead)->next;free(*pphead); // 在 C 语言中是安全的,但是 *pphead = tmp; 将会使头指针 pphead 指向 NULL,这在某些情况下可能是不期望的行为,比如如果链表设计为不允许为空*pphead = tmp;*/
}// 查找
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{SLNode* cur = phead;while (cur){if (cur->val == x){return cur;}else{cur = cur->next;}}return NULL;//没有此节点返回空
}// 插入,在pos的前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{assert(pphead);assert(pos);assert(*pphead);//assert((!pos && !(*pphead)) || (pos && *pphead));if (*pphead == pos){SLTPushFront(pphead, x);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLNode* newnode = CreatNode(x);newnode->next = pos;prev->next = newnode;}}//删除指定数pos
void SLTErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(*pphead);assert(pos);if (*pphead == pos){//头插SLTPopFront(pphead);}else{//pos不是第一个SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}}void SLTInsertAfter(SLNode* pos, SLNDataType x)
{assert(pos);SLNode* newnode = CreatNode(x);newnode->next = pos->next;pos->next = newnode;}void SLTEraseAfter(SLNode* pos)
{assert(pos);assert(pos->next);//pos->next->next;执行这句时如果pos->next为空,空指针没有next,会报错,所以先断言SLNode* tmp = pos->next;pos->next = pos->next->next;free(tmp);tmp = NULL;
}//依次释放单链表中的每个节点,直到整个链表被销毁
void SLTDestroy(SLNode** pphead)
{assert(pphead);SLNode* cur = *pphead;while (cur){SLNode* tmp = cur->next;free(cur);cur = tmp;}
}
5.调试
<Test.c>
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"SList.h"
#include<assert.h>void TestSLT1()
{SLNode* plist = NULL; // 错误,比如函数内部实现了地址交换,形参并不会影响实参,要实现地址交换,需要传二级指针SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);
}void TestSLT2()
{SLNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLNode* pos = SLTFind(plist, 3);SLTInsert(&plist, pos, 3);}void TestSLT3()
{SLNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLNode* pos = SLTFind(plist,4);SLTInsert(&plist, pos, 40);SLTPrint(plist);pos = SLTFind(plist, 2);SLTInsert(&plist, pos, 20);SLTPrint(plist);}void TestSLT4()
{SLNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLNode* pos = SLTFind(plist, 1);SLTErase(&plist, pos);SLTPrint(plist); pos = SLTFind(plist, 3);SLTErase(&plist, pos);SLTPrint(plist);
}int main()
{//TestSLT1();//TestSLT2();//TestSLT3();TestSLT4();return 0;
}