目录
1.链表
1.1 链表的概念及结构
2.不带头单链表的实现
2.1创建头文件“SList.h”
2.2 创建具体接口实现文件SList.c
2.2.1打印
2.2.2申请链表结点
2.2.3创建一个长度为n的链表
2.2.4尾插尾删
2.2.5头插头删
2.2.6寻找x元素,返回pos
2.2.7插入和删除pos之后的位置
2.2.8插入pos之前的位置和删除pos位置
2.2.9 销毁链表
3.主函数的实现
1.链表
1.1 链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链 接次序实现的
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向、双向
2. 带头、不带头
3. 循环、非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结 构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向 循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而 简单了,后面我们代码实现了就知道了。
2.不带头单链表的实现
2.1创建头文件“SList.h”
为什么要创立头文件
#pragma once #include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> typedef int SLTDataType; typedef struct SListNode { SLTDataType data; struct SListNode* next; }SLTNode; SLTNode* BuySLTNode(SLTDataType);//申请结点 SLTNode* CreateSList(int n);//创建一个多长的链表 void SLTPrint(SLTNode* phead); void SLTPushBack(SLTNode**pphead,SLTDataType x); void SLTPopBack(SLTNode**pphead); void SLTPushFront(SLTNode** pphead, SLTDataType x); void SLTPopFront(SLTNode** pphead); SLTNode* SListFind(SLTNode* plist, SLTDataType x);//找x的位置 void SListInsertAfter(SLTNode* pos, SLTDataType x);//插入pos之后的位置 void SListErasetAfter(SLTNode* pos);//删除pos后面的位置 void SListInsert(SLTNode**pphead,SLTNode* pos, SLTDataType); //插入pos之前的位置 void SLTErase(SLTNode** pphead,SLTNode* pos);//删除pos void SLTDestroy(SLTNode** pphead);//销毁链表
这里我们用来结构体的嵌套来定义单链表的节点结构体的嵌套定义
2.2 创建具体接口实现文件SList.c
先引用#include "SList.h"
2.2.1打印
void SLTPrint(SLTNode* phead) { SLTNode* cur = phead; /*if (cur == NULL) { printf("NULL"); }*/ while (cur) { printf("%d->", cur->data); cur = cur->next; } printf("NULL\n"); }
在这里我们没有断言assert,如果头指针为空指针,程序就会打印出NULL。
2.2.2申请链表结点
SLTNode* BuySLTNode(SLTDataType x)//申请结点 { SLTNode* newnode= (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { perror("melloc fail"); exit(-1); } newnode->data = x; newnode->next = NULL; return newnode; }
在这里我们用了扩容,但我们使用的是malloc,和顺序表有所不同的是我们并不需要异地扩容,对于链表来说存储的位置本就是随机的,不需要整块连续的空间。
2.2.3创建一个长度为n的链表
SLTNode* CreateSList(int n)//创建一个多长的链表 { SLTNode* phead = NULL, * ptail = NULL; int x = 0; for (int i = 0 ; i < n; i++) { SLTNode* newnode = BuySLTNode(i); if (phead == NULL) { phead = ptail = newnode; } else { ptail->next = newnode; ptail = newnode; } } return phead; }
2.2.4尾插尾删
void SLTPushBack(SLTNode** pphead, SLTDataType x) { SLTNode* cur =*pphead; SLTNode* newnode = BuySLTNode(x); if (*pphead == NULL) { *pphead =newnode; } else { while (cur->next) { cur = cur->next; } cur->next = newnode; } } void SLTPopBack(SLTNode** pphead) { assert(*pphead); SLTNode* tail = *pphead; SLTNode* prev = *pphead; if ((* pphead)->next == NULL) { free(*pphead); *pphead = NULL; } else { while (tail->next->next) { tail = tail->next; } free(tail->next); tail->next = NULL; } }
注意:
- 我们可以看到我们这里与顺序表明显不同的是我们这里传入了二级指针,这到底是为什么,看这篇博文单链表尾插过程中为什么传入二级指针
- 尾删时注意 free(tail)与 free(tail->next)的区别,其实原理和上述问题类似,因为后者修改的是结构体指针直接改变到了结构体,而仅仅free(tail)会导致出了函数的作用域,tail栈帧销毁,无法真正的改变到结构体
2.2.5头插头删
void SLTPushFront(SLTNode** pphead, SLTDataType x) { SLTNode* newnode = BuySLTNode(x); newnode->next = *pphead; *pphead = newnode; } void SLTPopFront(SLTNode** pphead) { assert(*pphead); SLTNode* cur = *pphead; cur = cur->next; free(*pphead); *pphead = cur; }
2.2.6寻找x元素,返回pos
SLTNode* SListFind(SLTNode* plist, SLTDataType x) { assert(plist); SLTNode* cur = plist; while (cur) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; }//找x的位置
2.2.7插入和删除pos之后的位置
void SListInsertAfter(SLTNode* pos, SLTDataType x) { /*if (pos == NULL) { SLTPushBack(pos, x); } else {*/ assert(pos); SLTNode*newnode=BuySLTNode(x); SLTNode* cur = pos; SLTNode* lnext = pos->next; cur->next = newnode; newnode->next = lnext; /*}*/ }//插入pos之后的位置 }
2.2.8插入pos之前的位置和删除pos位置
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) { assert(pos); if (*pphead == pos) { SLTPushFront(pphead, x); } else { SLTNode* prev = *pphead; SLTNode* newnode = BuySLTNode(x); /*while (prev->next) { if (prev->next == pos) { prev->next = newnode; newnode->next = pos; } prev = prev->next; }*/ while (prev->next->next != pos) { prev = prev->next; } prev->next = newnode; newnode->next = pos; } } //插入pos之前的位置 void SLTErase(SLTNode** pphead, SLTNode* pos)//删除pos { assert(pos);//写错了 if (pos== *pphead) { SLTPopFront(pphead); } else { SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } SLTNode* lnext = pos; prev->next = prev->next->next; free(pos); } }//删除pos
2.2.9 销毁链表
void SLTDestroy(SLTNode** pphead) { SLTNode* cur = *pphead; while (cur) { SLTNode* tail = cur->next; free(cur); cur = tail; cur = cur->next; } *pphead = NULL; }//销毁链表
注意置空,防止野指针,打印的时候没断言。
3.主函数的实现
#include "SList.h"
void SListTest1() {
SLTNode* n1 = BuySLTNode(1);
SLTNode* n2 = BuySLTNode(2);
SLTNode* n3 = BuySLTNode(3);
SLTNode* n4 = BuySLTNode(4);
SLTNode* n5 = BuySLTNode(5);
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = n5;
n5->next = NULL;
// SLTNode*plist=CreateSList(5);
SLTPrint(n1);
}
void SListTest2() {
SLTNode* plist = CreateSList(1);
SLTPrint(plist);
/*SLTPushBack(&plist, 100);*/
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPushFront(&plist,9);
SLTPushFront(&plist,99);
SLTPushFront(&plist,999);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
}
void SListTest3() {
SLTNode* plist = CreateSList(5);
SLTPrint(plist);
/*SLTNode*p=SListFind(plist, 3);
SListInsertAfter(p, 100);
SLTPrint(plist);
SListErasetAfter(p);
SLTPrint(plist);*/
SLTNode*p = SListFind(plist, 0);
/*SListInsert(&plist, p, 999);
SLTPrint(plist);*/
SLTErase(&plist, p);
SLTPrint(plist);
SLTDestroy(&plist);
SLTPrint(plist);
}
int main()
{
SListTest1();
SListTest2();
SListTest3();
return 0;
}