1.链表
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
2.链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
①单向或者双向
②带头或者不带头
③循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
- 无头单向非循环链表
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
小记tip:
全局变量或者静态存储都在数据段上,而在函数内部临时开辟的则存储在栈区(记忆技巧:栈区后来先出,生命周期短,适合存储临时使用的变量,在函数执行结束时会自动被释放),堆区存放的则是在函数运行过程中开辟出来的空间,需要程序员进行手动分区以及释放,也可能程序结束时由OS回收,所以也可能会发生内存泄漏问题
3.无头单向非循环链表
3.1接口实现
<SList.h>
#pragma once
typedef 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); //销毁整个单链表
3.2函数实现
<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;
}
}
3.3调试
<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;
}
4.带头双向循环链表
4.1接口实现
<List.h>
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 带头双向循环链表
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType val;
}LTNode;
LTNode* CreatLTNode(LTDataType);//创建新节点
LTNode* LTInit();//初始化
void LTPrint(LTNode* phead);//打印
void LTPushBack(LTNode* phead, LTDataType x);//尾插
void LTPopBack(LTNode* phead);//尾删
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPopFront(LTNode* phead);//头删
LTNode* LTFind(LTNode* phead, LTDataType x);// 查找
void LTInsert(LTNode* pos, LTDataType x);// 在pos前面插入
void LTErase(LTNode* pos);// 删除pos位置
void LTDestroy(LTNode* phead);//销毁链表
4.2函数实现
<List.c>
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"List.h"
LTNode* CreatLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
LTNode* LTInit()
{
LTNode* phead = CreatLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
void LTPrint(LTNode* phead)
{
assert(phead);
printf("哨兵卫");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
printf("\n");
}
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = CreatLTNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* tail = phead->prev;
phead->prev = tail->prev;
tail->prev->next = phead;
free(tail);
//tail = NULL;//tail变量会自动销毁,不需要写这行
}
//头插,也可以再创建一个指针保存phead的下一个节点,转换为三个节点的地址变换
//结构优势(C++ STL):如果链表为空,只有一个哨兵卫头结点,first就是自己
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = CreatLTNode(x);
LTNode* first = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);//如果只有头结点phead,first = phead,不能free,否则出现野指针
LTNode* first = phead->next;
LTNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 在pos前面插入
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = CreatLTNode(x);
LTNode* posPrev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
newnode->prev = posPrev;
posPrev->next = newnode;
}
// 删除pos位置
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posNext = pos->next;
LTNode* posPrev = pos->prev;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
4.3调试
<Test.c>
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"List.h"
void Test1()
{
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPushBack(plist, 5);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);
}
void Test2()
{
LTNode* plist = LTInit();
LTPushFront(plist, 10);
LTPushFront(plist, 20);
LTPushFront(plist, 30);
LTPushFront(plist, 40);
LTPrint(plist);
LTPopFront(plist);
LTPrint(plist);
LTPopFront(plist);
LTPrint(plist);
}
void Test3()
{
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPushBack(plist, 5);
LTPrint(plist);
LTNode* pos = LTFind(plist, 3);
if (pos)
{
pos->val *= 10;
}
LTPrint(plist);
LTInsert(pos, 400);
LTPrint(plist);
}
void Test4()
{
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPushBack(plist, 5);
LTPrint(plist);
LTNode* pos = LTFind(plist, 3);
if (pos)
{
LTErase(pos);
}
LTPrint(plist);
}
int main()
{
/*Test1();*/
/*Test2();*/
/*Test3();*/
Test4();
return 0;
}