1 链表
1.1 链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
无论是【顺序表】还是【链表】里的数据,任何类型都可。所以用typedef。
在开始阶段,线性表可能是物理空间上连续【顺序表】,可能是逻辑顺序上连续【链表】。
链表的优势:删除和插入数据不需要挪动,空间可以一块一块的释放,不会影响其他节点。链表每个节点都是独立的。
1.2 链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1.
单向或者双向
2. 带头或者不带头
3. 循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1.
无头单向非循环链表
:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2.
带头双向循环链表
:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带
来很多优势,实现反而简单了,后面我们代码实现了就知道了。
1.3 链表的实现
主函数
Test.c
#include"SList.h"
int main()
{
SLNode* phead = NULL;//结构体指针变量存放结构体的地址 头节点
test1(&phead);//测试尾插
test2(&phead);//测试头插
test3(&phead);//测试尾删
test4(&phead);//测试头删
return 0;
}
test1 测试尾插
void test1(SLNode** pphead)//测试尾插
{
SLPushBack(pphead, 10);
SLPushBack(pphead, 20);
SLPushBack(pphead, 30);
SLPushBack(pphead, 40);
SLPrint(*pphead);
}
test2 测试头插
void test2(SLNode** pphead)//测试头插
{
SLPushFront(pphead, 77);
SLPushFront(pphead, 66);
SLPushFront(pphead, 55);
SLPushFront(pphead, 33);
SLPrint(*pphead);
}
test3 测试尾删
void test4(SLNode** pphead)//测试尾删
{
SLPopBack(pphead);
SLPopBack(pphead);
SLPrint(*pphead);
}
test4 测试头删
void test3(SLNode** pphead)//测试头删
{
SLPopFront(pphead);
SLPopFront(pphead);
SLPopFront(pphead);
SLPrint(*pphead);
}
test5 测试查找+在前面插入
void TestSLT5()
{
SLNode* plist = NULL;
//SLTInsert(&plist, NULL, 1);
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);
}
test6 测试删除pos位置
void TestSLT6()
{
SLNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLNode* pos = SLTFind(plist, 1);
SLTErase(&plist, pos);
SLTPrint(plist);
}
test7 测试在pos后面插入
void TestSLT7()
{
SLNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLNode* pos = SLTFind(plist, 1);
SLTInsertAfter(pos, 10);
SLTPrint(plist);
SLTDestroy(&plist);
}
头文件&函数声明SList.h
头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
函数声明SList.h
创建单链表
//创建单链表
typedef int SLNDataType;//单链表节点数据类型
typedef struct SListNode//创建节点
{
SLNDataType val;
struct SListNode* next;
}SLNode;
为什么 SListNode 还未创建好,就可以在结构体内部使用这个 SListNode 了
因为next是一个结构体指针变量,主体是指针变量,无影响。但是如果是 struct SListNode next;不可以,结构体嵌套结构体是不可以的。
打印数据
//打印数据
void SLPrint(SLNode* phead);
尾插
//尾插
void SLPushBack(SLNode** pphead, SLNDataType x);
头插
//头插
void SLPushFront(SLNode** pphead, SLNDataType x);
尾删
//尾删
void SLPopBack(SLNode** pphead);
头删
//头删
void SLPopFront(SLNode** pphead);
查找
//查找
SLNode* SLTFind(SLNode* phead, SLNDataType x);
在pos的前面插入
// 在pos的前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);
删除pos位置
// 删除pos位置
void SLTErase(SLNode** pphead, SLNode* pos);
后面插入
// 后面插入
void SLTInsertAfter(SLNode* pos, SLNDataType x);
后面删除
//后面删除
void SLTEraseAfter(SLNode* pos);
空间释放
void SLTDestroy(SLNode** pphead);
函数实现SList.c
#include"SList.h"
打印SLPrint
void SLPrint(SLNode* phead)
{
assert(phead);
SLNode* tail = phead;
printf("phead->");
while (tail->next != NULL)
{
printf("%d->", tail->val);
tail = tail->next;
}
printf("NULL");
printf("\n");
}
注意:要固定住phead不动!
创建节点CreateNode
//创建链表的节点---结构体
SLNode* CreateNode(SLNDataType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc");
exit(-1);//直接终止程序
//return;
}
newnode->val = x;
newnode->next = NULL;
return newnode;
}
尾插
//尾插
void SLPushBack(SLNode** pphead, SLNDataType x)
{
//assert(*pphead);
SLNode* newnode = CreateNode(x);
//无节点
if (*pphead == NULL)
{
*pphead = newnode;
}
//多个节点
else
{
SLNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
注意:
- 这里的形参是二级指针。如果使用一级指针,就会链接不起来,出了函数栈帧局部变量就销毁了。
- 改变外部的变量,一定有一个解引用的操作
这里可以写成这样吗?
while (tail != NULL)
{
tail = tail->next;
}
tail = newnode;
当然是不可以的,因为这样写会导致最后一个节点还是指向MULL,newnode只传给了一个局部变量。
- 有一个或以上的节点
- 没有节点
头插
//头插
void SLPushFront(SLNode** pphead, SLNDataType x)
{
//assert(*pphead);
SLNode* newnode = CreateNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
这段代码对于空链表也适用。
尾删
//尾删
void SLPopBack(SLNode** pphead)
{
assert(*pphead);
//一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLNode* tail = *pphead;
SLNode* prve = tail;
while (tail->next != NULL)
{
prve = tail;
tail = tail->next;
}
prve->next = NULL;
free(tail);
tail = NULL;
}
}
头删
//头删
void SLPopFront(SLNode** pphead)
{
assert(*pphead);
SLNode* tmp = *pphead;
*pphead = (*pphead)->next;
free(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)
{
// 严格限定pos一定是链表里面的一个有效节点
assert(pphead);
assert(pos);
assert(*pphead);
if (*pphead == pos)
{
// 头插
SLTPushFront(pphead, x);
}
else
{
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLNode* newnode = CreateNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
删除pos位置
void SLTErase(SLNode** pphead, SLNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
if (*pphead == pos)
{
// 头删
SLTPopFront(pphead);
}
else
{
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 = CreateNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
后面删除
void SLTEraseAfter(SLNode* pos)
{
assert(pos);
assert(pos->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* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}