2.3.2_2 单链表的查找
2.3.2_3 单链表的建立
- 尾插法
LinkList List_TailInsert(LinkList &L){
int x; //设ElemType为整型
L = (LinkList)malloc(sizeof(LNode));//建立头节点,初始化空表
LNode *s,*r = L; //r为表尾指针
scanf("%d",&x); //输入节点的值
while (x != 999){
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
r->next = s; //在r节点之后插入元素x
r = s;
scanf("%d",&x);
}
r->next = NULL; //尾节点指针置空
return L;
}
- 头插法
//头插法建立单链表:
//初始化单链表
while 循环{
每次取一个数据元素e;
InsertNextNode(L,e);
}
//后插操作:在p节点之后插入元素e
bool InsertNextNode (LNode *p,ElemType e){
if (p == NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s == NULL) //内存分配失败
return false;
s->data = e;
s->next = p->next;
p->next = s; //将节点s连接到p之后
return ture
}
//方法二:
LinkList List_HeadInsert(LinkList &L){//逆向建立单链表
LNode *s;
int x;
L = (LinkList)malloc(sizeof(LNode));//建立头节点
L->next = NULL //初始化为空链表
scanf("%d",&x); //输入节点的值
while(x != 999){
s = (LNode *)malloc(sizeof(LNode));//创建新节点
s->data = x;
s->next = L->next;
L->next = s; //将节点插入表中,L为头指针
scanf("%d",&x);
}
return L;
}
头插法的重要应用:链表逆置
养成好习惯:在初始化单链表时,都要把头指针指向NULL。
2.3.3 双链表
//双链表
typedef struct DNode{//Double定义双链表节点类型
ElemType data; //数据域
struct DNode *prior,*next; //前驱指针和后继指针
}DNode,*DLinkList;
//双链表的初始化(带头节点)
//初始化双链表
bool InitDLinkList(DLinkList &L){
L = (DNode *)malloc(sizeof(DNode));
if (L == NULL) //内存不足,分配失败
return false;
L->prior = NULL; //头节点的prior永远指向NULL
L->next = NULL; //头节点之后暂时还没有节点
return true;
}
void testDLinkList(){
//初始化双链表
DLinkList L;
InitDLinkList(L);
}
//判断双链表是否为空(带头节点)
bool Empty(DLinkList L){
if(L->next == NULL)
return true;
else
return false;
}
//双链表的插入
//在p节点之后插入s节点,最好结合ppt
bool InsertNextDNode(DNode *p,DNode *s){
s->next = p->next;
p->next->prior = s;这句话需要改进
s->prior = p;
p->next = s;
}
//但是如果p节点刚好是最后节点,则会出现问题,写得更严谨:
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的后继节点q
p->next = q->next;
q->next->prior = p;//这句还是存在问题,若q没有后继节点
free(q);
//修改:
bool DeleteNextDNode(DNode *p){
if(p == NULL) return false;
DNode *q = p->next; //找到p的后继节点q
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
}
}
//双向链表的遍历
//1.后向遍历
while( p != NULL){
//对节点p做相应处理,如打印
p = p->next;
}
//2.前向遍历
while(p != NULL){
//对节点p做相应处理
p = p->prior;
}
3.前向遍历(跳过头节点)
while(p->prior != NULL){
//p作处理
p = p->prior;
}
2.3.4循环链表
- 循环单链表
//循环单链表
//(和普通单链表区别:普通-尾指针指向NULL;循环单链表-尾指针指向头节点)
typedef struct LNode{ //定义单链表节点类型
ElemType data; //每个节点存放一个数据元素
struct LNode *next; //指针指向下一个节点
}LNode,*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{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
//初始化空的循环双链表
bool InitDLinkList(DLinkList &L){
L = (DNode *)malloc(sizeof(DNode));
if(L == NULL)
return false;
L->prior = L;
L->next = L;//头节点的prior,next指针均指向头节点
return true;
}
void textDLinkList(){
DLinkList L;
InitDLinkList(L);
}
//判断循环双链表是否为空
bool Empty(DLinkList L){
if(L->next == L)
return == true;
else
return false;
}
//判断节点p是否是循环双链表的表尾节点
bool isTail(DLinkList L,DNode *p){
if(p->next == L)
return true;
else
return false;
}
2.3.5静态链表
//用代码定义一个静态链表
#define Maxsize 10;//静态链表的最大长度
struct Node
{
ElemType data;
int next; //下一个元素的数组下标
};
void testSLinkList(){
struct Node a[Maxsize];
//...后续代码
}
文件分配表FAT就是一个静态链表
2.3.6顺序表和链表的比较
- 顺序表静态分配:静态数组(声明一个数组或者变量:系统自动回收空间)
- 顺序表动态分配:动态数组(需要使用malloc,free,手动free,系统堆区)
- 链表(需要使用malloc,free,手动free)
关于顺序表和链表的增删操作
1.虽然时间复杂度都是O(n),但是顺序表主要用于数据移动,而链表主要用于查找目标元素;有时候数据元素很大