链表
单链表
typedef struct LNode{
int data; //结点的数据域
struct LNode *next; //结点的指针域
}LNode,*LinkList; //LinkList为指向结构体的指针类型
LNode *L; //声明一个指向单链表第一个结点的指针
或:LinkList L; //声明一个指向单链表第一个结点的指针(代码可读性更强)
LinkList
与LNode*
两者本质上是等价的,习惯上用LinkList
定义单链表,强调定义的是某个单链表的头指针;用LNode*
定义指向单链表中任意结点的指针变量
初始化
不带头结点的单链表
头指针指向的结点就存放数据
#include<stdio.h>
#include<stdlib.h>
//由于C语言中没有布尔类型,因此我们可以这样替换
typedef int bool;
#define true 1
#define false 0
typedef struct LNode{
int data; //结点的数据域
struct LNode *next; //结点的指针域
}LNode,*LinkList; //LinkList为指向结构体的指针类型
//初始化一个单链表(带头结点)
bool InitList(LinkList *L){
L = NULL;
return true;
}
//判断单链表是否为空
bool Empty(LinkList *L){
if(L == NULL)
return true;
else
return false;
}
int main(){
LinkList L; //声明一个指向单链表的指针
InitList(&L); //初始化一个空表
//LNode *p = (LNode*)malloc(sizeof(LNode));
return 0;
}
带头结点的单链表
生成新节点作为头结点,头指针指向头结点,头节点不存放数据
#include<stdio.h>
#include<stdlib.h>
//由于C语言中没有布尔类型,因此我们可以这样替换
typedef int bool;
#define true 1
#define false 0
typedef struct LNode{
int data; //结点的数据域
struct LNode *next; //结点的指针域
}LNode,*LinkList; //LinkList为指向结构体的指针类型
//初始化一个单链表(带头结点)
bool InitList(LinkList *L){
L = (LNode *)malloc(sizeof(LNode)); //分配一个头节点
if(L == NULL) //内存不足,分配失败
return false;
L->next = NULL; //头节点之后暂时还没有结点
return true;
}
//判断单链表是否为空
bool Empty(LinkList *L){
if(L->next == NULL)
return true;
else
return false;
}
int main(){
LinkList L; //声明一个指向单链表的指针
InitList(&L); //初始化一个空表
return 0;
}
插入
按位序插入
带头结点
bool ListInsert(LinkList *L,int i,int e){
if(i < 1)
return false;
LNode *p; //指针p指向当前扫描的结点
int j = 0; //当前p指向的是第几个结点(初始指向第0个结点即头结点)
p = L;
while(p != NULL && j < i-1){
p = p->next;
j++;
}
if(p==NULL) //i值不合法
return false;
//新建一个结点然后插入L中
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s; //将结点s连到p之后
return true; //插入成功
}
不带头结点
bool ListInsert(LinkList *L,int i,int e){
if(i < 1)
return false;
//只有这里插入在首位时
if(i == 1){
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s;
return true;
}
LNode *p; //指针p指向当前扫描的结点
int j = 1; //当前p指向的是第几个结点(初始指向第0个结点即头结点)
p = L;
while(p != NULL && j < i-1){
p = p->next;
j++;
}
if(p==NULL) //i值不合法
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s; //将结点s连到p之后
return true; //插入成功
}
指定结点的后插操作
bool InsertNextNode(LNode *p,int e){
if(p == NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s == NULL) // 内存分配失败
return false;
s->data = e; //用新结点保存元素e
s->next = p->next;
p->next = s; //将结点s连到p之后
return true;
}
指定结点的前插
通过指定结点只能找到后继的结点,不能逆向往前找,要想在指定结点前面插入,可以再传入链表头指针,或者通过下面这种比较巧妙的方法,在指定结点之后先插入一个结点,然后再交换两个结点的值,就相当于在指定节点前插入
bool InsertPriorNode(LNode *p,int e){
if(p == NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s == NULL)
return false;
s->next = p->next;
p->next = s; //将新结点s连接到p之后
s->data = p->data; //将p中元素复制到s中
p->data = e; //p中元素覆盖为e
return true;
}
删除(带头结点)
按位序删除
bool ListDelete(LinkList *L,int i){
if(i < 1)
return false;
LNode *p; //指针p指向当前扫描的结点
int j = 0; //当前p指向的是第几个结点(初始指向第0个结点即头结点)
p = L;
while(p != NULL && j < i-1){
p = p->next;
j++;
}
if(p==NULL) //i值不合法
return false;
if(p->next == NULL)
return false;
LNode *q = p->next; //令q指向被删除结点
int e = q->data; //用e返回元素的值,可用作返回使用
p->next = q->next; //将*q结点从链中断开
free(q); //释放结点的存储空间
return true; //删除成功
}
指定结点的删除
删除结点时需要将删除结点的前驱和后继结点连接,但我们得知前驱结点,因此,因此我们将需要删除的结点p与第p+2个结点连接(删除第p+1个结点),再将第p+1个结点的值赋给第p个结点,这样就相当于删除了第p个结点。
bool DeleteNode(LNode *p){
if(p==NULL)
return false;
LNode *q = p->next; //令q指向*p的后继节点
p->data = p->next->data; //和后继结点交换数据域
p->next = q->next; //将*p结点从链中断开
free(q);
return true;
}
这种写法存在bug,如果要删除的结点为最后一个结点就会出现问题
查找(带头结点)
按位查找
LNode * GetElem(LinkList L,int i){
if(i < 0)
return NULL;
LNode *p;
int j = 0;
p = L;
while(p != NULL && j < i){
p = p->next;
j++;
}
return p;
}
按值查找
LNode * LocateElem(LinkList L,int e){
LNode *p = L->next; //查找操作从第一个结点开始
while(p != NULL && p->data != e){
p = p->next;
}
return p; //找到返回结点指针,否则返回NULL
}
求单链表长度
int Length(LinkList L){
int len = 0;
LNode *p = L;
while(p->next != NULL){
p = p->next;
len++;
}
return len;
}
单链表的建立
尾插法建立单链表
可以利用上面提到的按位插入操作,插入位置每次都是表尾,这种方式每次插入时都要从表头位置遍历到表尾位置,时间复杂度为O(n2)
我们还可以设置一个尾指针,初始时指向头结点,每生成一个新结点插入尾指针之后,将尾指针后移,再次移到尾部
//n为链表长度
void CreateList_R(LinkList *L,int n){
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
r = L;
for(int i = 0; i < n; i++){
p = (LNode *)malloc(sizeof(LNode)); //创建一个新结点
scanf("%d",&(p->data)); //输入新结点数据域的值
p->next = NULL; //将结点指针域指向NULL
r->next = p; //将结点连接到链表上
r = p; //将r指向链表尾部
}
}
前插法建立单链表
void CreateList_H(LinkList *L,int n){
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
for(int i = 0; i < n; i++){
p = (LNode *)malloc(sizeof(LNode)); //创建一个新结点
scanf("%d",(&p->data)); //输入新结点数据域的值
p->next = L->next; //将新结点放在第一个第一个结点之前
L->next = p; //将头结点的指针指向新结点
}
}
双链表
typedef struct DNode{
int data; //结点的数据域
struct DNode *prior; //指向结点的直接前驱
struct DNode *next; //指向结点的直接后继
}DNode,*DLinkList; //LinkList为指向结构体的指针类型
初始化
#include<stdio.h>
#include<stdlib.h>
typedef int bool;
#define true 1
#define false 0
typedef struct DNode{
int data; //结点的数据域
struct DNode *prior; //指向结点的直接前驱
struct DNode *next; //指向结点的直接后继
}DNode,*DLinkList; //LinkList为指向结构体的指针类型
bool InitDLinkList(DLinkList *L){
L = (DNode *)malloc(sizeof(DNode)); //分配一个头结点
if(L == NULL)
return false;
L->prior = NULL; //头结点的prior永远指向NULL
L->next = NULL; //头结点之后暂时没有结点
return true;
}
//判断双链表是否为空
bool Empty(DLinkList L){
if(L->next == NULL)
return true;
else
return false;
}
int main(){
//初始化双链表
DLinkList L;
InitDLinkList(L);
return 0;
}
插入
后插操作
//在p结点之后插入s结点
bool InsertNextDNode(DNode *p,DNode *s){
if(p == NULL || s == NULL)
return false;
s->next = p->next;
if(p->next != NULL)
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
前插操作
在结点p之前插入结点可以转化为结点p前一个结点的后插操作
删除
//删除结点p的后继结点
bool DeleteNextDNode(DNode *p){
if(p == NULL)
return false;
DNode *q = p->next;
if(q == NULL)
return false;
p->next = q->next;
if(q->next != NULL)
q->next->prior = p;
free(q);
return true;
}
void DestoryList(DLinkList *L){
//循环释放各个数据结点
while(L->next != NULL)
DeleteNextDNode(L);
free(L); //释放头结点
L = NULL; //头指针指向NULL
}
遍历
//后向遍历
while(p != NULL){
//对结点p做相应处理,如打印
p = p->next;
}
//前向遍历
while(p != NULL){
//对结点p做相应处理
p = p->prior;
}
//前向遍历(跳过头结点)
while(p->prior != NULL){
//对结点p做相应处理
p = p->prior;
}
循环链表
循环单链表
普通单链表表尾结点的next指针指向NULL,循环单链表表尾结点的next指针指向头结点
typedef struct LNode{
int data; //结点的数据域
struct LNode *next; //结点的指针域
}LNode,*LinkList; //LinkList为指向结构体的指针类型
//初始化一个单链表(带头结点)
bool InitList(LinkList *L){
L = (LNode *)malloc(sizeof(LNode)); //分配一个头结点
if(L == NULL)
return false;
L->next = L; //头结点的next指向头结点
return true;
}
//判断单链表是否为空
bool Empty(LinkList *L){
if(L->next == L)
return true;
else
return false;
}
//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L, LNode *p){
if(p->next == L)
return true;
else
return false;
}
循环双链表
typedef struct DNode{
int data; //结点的数据域
struct DNode *prior; //指向结点的直接前驱
struct DNode *next; //指向结点的直接后继
}DNode,*DLinkList; //LinkList为指向结构体的指针类型
bool InitDLinkList(DLinkList *L){
L = (DNode *)malloc(sizeof(DNode)); //分配一个头结点
if(L == NULL) //内存不足分配失败
return false;
L->prior = L; //头结点的prior永远指向头结点
L->next = L; //头结点的next指向头结点
return true;
}
//判断循环双链表是否为空
bool Empty(DLinkList L){
if(L->next == L)
return true;
else
return false;
}
//在p结点之后插入s结点
bool InsertNextDNode(DNode *p,DNode *s){
if(p == NULL || s == NULL)
return false;
s->next = p->next;
if(p->next != NULL)
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
//判断结点p是否为循环双链表的表尾结点
bool isTail(DLinkList L,DNode *p){
if(p->next == L)
return true;
else
return false;
}
静态链表
静态链表时用数组的方式实现链表
单链表各个结点在内存中散落分布
静态链表分配一整片连续的内存空间,各个结点集中放置
优点:增删操作不需要大量移动元素
缺点:不能随机存取,只能从头结点开始依次往后查找;容量固定不可变
#define MaxSize 10 //静态链表最大长度
struct Node{
//静态链表结构类型定义
int data; //存储数据元素
int next; //下一个元素的数组下标
};
//可用SLinkList定义“一个长度为MaxSize的Node型数组”
typedef struct Node SLinkList[MaxSize];
void testLinkList(){
struct Node a[MaxSize]; //数组a作为静态链表
}
等价于
#define MaxSize 10 //静态链表最大长度
typedef struct{
int data;
int next;
}SLinkList[MaxSize];
void testLinkList(){
SLinkList a;
}