1 单链表
1.1 初始化
1.1.1 不带头结点的单链表
typedef struct LNode{ //定义单链表结点类型
ElemType data;
struct LNode *next; //指针指向下一个结点
}LNode, *LinkList;
//初始化一个空的单链表(不带头结点)
bool InitList(LinkList &L) {
L = NULL; //空表,暂时没有任何结点
return true;
}
//判断单链表是否为空
bool Empty (LinkList L) {
if (L == NULL)
return true;
else
return false;
}
void main(){
LinkList L; //声明一个指向单链表的指针
//初始化一个空表
InitList(L):
//……
}
注:
- 此操作只是声明一个指向单链表的指针,然后使该指针指向空,这个单链表里面还没有结点
- 检查此时单链表是否为空,只需要看L这个指针是否为空即可
- L即为头指针
- 当单链表存放数据时,头指针指向第一个实际存放数据的结点
1.1.2 带头结点的单链表
typedef struct LNode{ //定义单链表结点类型
ElemType data;
struct LNode *next; //指针指向下一个结点
}LNode, *LinkList;
//初始化一个单链表(带头结点)
bool InitList(LinkList &L) {
L = (LNode*)malloc(sizeof(LNode)); //分配一个头结点,头指针L指向头结点
if (L == NULL)
return false; //头指针后面应该有头结点,如果没有,说明空间不足,头结点分配失败
L->next = NULL; //头结点之后是空的,说明还没有其他结点
return true;
}
//判断单链表是否为空
bool Empty (LinkList L) {
if (L->next == NULL) 头结点之后是空的,说明还没有其他结点
return true;
else
return false;
}
void main(){
LinkList L; //声明一个指向单链表的指针
//初始化一个空表
InitList(L):
//……
}
注:
- 头指针L指向头结点
- 头结点中还没有数据
- 判断此时单链表是否为空,只需判断头结点之后是否为空
- 当单链表存放数据时,头指针指向头结点,第一个带数据的结点在头结点之后
1.1.3 LNode 、LNode * 、LinkList的联系与区别
1、LNode 是struct LNode这个结构体的别名,而这个结构体是用来创建单链表的结点。
2、LinkList 是指向单链表结点的指针
3、LNode* 等价于LinkList
4、当强调这是结点时使用LNode* ;强调这是单链表时使用LinkList
1.2 按位序插入
1.2.1 带头结点的单链表——O(n)
思想:
- 判断插入的位序 i 是否合法,若此单链表结点的个数为n,则可插入的位序的范围为(1,n+1)
- 循环找到第 i -1 个结点
- 为新插入的结点分配空间,并将数据赋给新结点
- 将新结点插入
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode, *LinkList;
//按位序插入——带头结点
//在第i个位置插入元素e
bool ListInsert (LinkList &L, int i, ElemType e){
if( i<1 ){ //【1】判断插入的位序是否>=1
return false;
}
LNode *p;
int j=0;
p = L; //L为头结点,头结点是第0个结点,第一个结点是存放数据的结点
while ( p!=NULL && j<i-1){ //【2】循环找到第i-1个结点
p = p->next;
j++;
}
if (p == NULL){ //【1】判断插入的位序是否<=n
return false;
}
LNode *s = (LNode*)malloc(sizeof(LNode)); //【3】为新结点分配空间,并定义指针s指向新结点
s->data = e; //【3】将数据赋值给新结点
s->next = p->next; //【4】插入新结点
p->next = s;
return true;
}
注:
- 判断插入的位序是否符合(1,n+1)时,由于单链表的结点个数n是未知的,所以两个条件分别进行判断
- 插入的两行代码的顺序不可变换
1.2.2 不带头结点的单链表——O(n)
思想:
- 判断插入的位序 i 是否 >= 1
- 由于不带头结点的单链表的头指针是指向第一个结点的,因此,若插入的结点为第一个结点时,插入操作结束后,还需将头指针重新指向新插入的结点。因此,对于 i = 1 时,单独操作。
- i > 1的操作与带头结点的步骤相同
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode, *LinkList;
//2、按位序插入——不带头结点
//在第i个位置插入元素e
bool ListInsert (LinkList &L, int i, ElemType e){
if( i<1 ){ //【1】判断插入的位序是否>=1
return false;
}
if ( i == 1 ){
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s; //头指针指向新结点
return true;
}
LNode *p;
int j=1; //注意:j的值和带头结点的单链表的值的操作中不同
p = L; //p指向第一个结点
while ( p!=NULL && j<i-1){ //【2】循环找到第i-1个结点
p = p->next;
j++;
}
if (p == NULL){ //【1】判断插入的位序是否<=n
return false;
}
LNode *s = (LNode*)malloc(sizeof(LNode)); //【3】为新结点分配空间,并定义指针s指向新结点
s->data = e; //【3】将数据赋值给新结点
s->next = p->next; //【4】插入新结点
p->next = s;
return true;
}
注:
- 注意 j 的值在带头结点的单链表和不带头结点的单链表中的区别。
- 带头结点的单链表中,有第0个结点,即头结点;不带头结点的单链表中直接从第一个结点开始。 j 代表的就是指针 p 指向的是第几个结点。
1.2.3 指定结点的后插——O(1)
思想:
- 判断指定的结点p是否存在
- 为新元素e分配空间,并检测是否分配成功
- 插入
//指定结点的后插操作
//在p结点之后插入元素e
bool insertNextNode (LNode *p, ElemType e){
if (p == NULL){ //【1】判断p结点是否存在
return false;
}
LNode *s = (LNode*)malloc(sizeof(LNode)); //【2】为新结点分配空间,并定义指针s指向新结点
if (s == NULL){
return false;
}
s->data = e; //【3】将数据赋值给新结点
s->next = p->next; //【4】插入新结点
p->next = s;
return true;
}
注:
- 其实按位序插入就是对第i-1个元素进行后插操作,可在循环找到第i-1个元素后,直接调用后插操作函数进行。
1.2.4 指定结点的前插——O(1)
思想:
- 将新结点s插在指定结点p的后面
- 将s和p的数据对换
//指定结点的前插操作
//在p结点之前插入元素e
bool InsertPriorNode (LNode *p, ElemType e){
if (p == NULL){ //【1】判断p结点是否存在
return false;
}
LNode *s = (LNode*)malloc(sizeof(LNode));
if (s == NULL){
return false;
}
s->next = p->next; //【3】将新结点s插在p结点之后
p->next = s;
s->data = p->data; //【4】将p结点、s结点的数据对换
p->data = e;
return true;
}
注:
- 前插有两种思想,一是找到指定结点的前驱结点,再对前驱结点进行后插;二是将新结点插在指定结点之后,再将两个结点的数据对换
- 由于单链表想找到指定结点的前驱结点,只能在已知头指针的单链表中循环遍历找到,且时间负责度为O(n)。若头指针不可知,则无法找到。所以使用思想二更好。
1.3 删除
1.3.1 带头结点的按位序删除——O(n)
思想:
- 找到第i-1个元素
- 判断待删除的第i个元素以及第i-1个元素是否存在
- 将第i+1个元素连接到第i-1个元素后
- 返回第i个元素的值,并释放其存储空间
//删除第i个元素
bool ListDelete (LinkList &L, int i, ElemType e){
if( i<1 ){
return false;
}
LNode *p; //找到第i-1个元素
int j=0;
p = L;
while ( p!=NULL && j<i-1){
p = p->next;
j++;
}
if (p == NULL){ //判断第i-1个元素
return false;
}
if (p->next == NULL){ //判断第i个元素
return false;
}
LNode *q = p->next; //用指针q指向第i个元素
e = q->data; //返回被删除元素的数据
p->next = q->next; //第i+1个元素连接到第i-1个元素之后
free(q); //释放第i个元素
return true;
}
1.3.2 删除指定结点——O(n)
思想:
- 将结点p+1和结点p的数据交换
- 再将p+2连接到p结点之后
//删除指定结点p
bool DeleteNode (LNode *p){
if ( p == NULL )
return false;
LNode *q = p->next; // q 指向 p 的后继结点
p->data = p->next->data; // p 和 p+1 的数据交换
p->next = q->next; // p+2 连接到 p 后面
free(q);
return true;
}
注:
- 若删除的结点p是最后一个结点,只能从头开始检索第p-1个结点——O(n)
1.4 查找
1.4.1 按位查找——O(n)
//按位查找
LNode * GetElem (LinkList L, int i){
if(i<0)
return NULL;
LNode *p;
int j=0; //j表示p指向的是第几个结点
p = L; //p指向头结点,即第0个结点
while ( p!=NULL && j<i-1){
p = p->next;
j++;
}
return p; //若查找的i>n,返回的p也是NULL
}
1.4.2 按值查找——O(n)
//按值查找
//找到数据为e的结点
LNode * LocaleElem( LinkList L, ElemType e ){
LNode *p = L->next; //p为头结点之后的第一个结点
while ( p != NULL && p->data != e)
p = p->next;
return p;
}
1.5 求表长——O(n)
//求表长
int Length(LinkList L){
int len = 0;
LNode *p = L;
while (p->next != NULL){
p = p->next;
len++;
}
return len;
}
1.6 单链表的建立
1.6.1 尾插法
思想:
- 初始化一个空表,这里通过建立头结点来做到
- 每次输入的数据都存在结点s中
- 每个结点建立完毕后,插到尾指针之后
- 尾指针永远指向最后一个新建立的结点s
//尾插法建立单链表
LinkList Lis_TailInsert(LinkList &L){
int x; //设ElemType为整形
L = (LinkList)malloc(sizeof(LNode)); //建立头指针,即初始化空表
L->next = NULL;
LNode *s, *r=L; //r为尾指针
scanf("%d", &x);
while( x!= 9999 ){ //输入9999表示结束,也可使用其他数字来代替999
s = (LNode*)malloc(sizeof(LNode)); //建立新结点
s->data = x;
r->next = s; //在结点r之后插入新结点
r = s; //尾指针指向新结点,即尾指针永远指向最后一个结点
scanf("%d", &x);
}
r->next = NULL; //尾结点指针指空
return L;
}
1.6.2 头插法
思想:
- 初始化空链表
- 建立新结点
- 对头指针做后插操作
//头插法建立单链表
LinkList List_HeadInsert(LinkList &L){
LNode *s;
int x;
L = (LinkList)malloc(sizeof(LNode)); //初始化空链表,L为头指针
L->next = NULL;
scanf("%d", &x);
while(x != 9999){
s = (LNode*)malloc(sizeof(LNode)); //建立新结点
s->data = x;
s->next = L->next; //对头指针进行后插
L->next = s;
scanf("%d", &x);
}
return L;
}
注:
- 头插法的应用——链表的逆置
2 双链表
2.1 结点的定义
//双链表结点的定义
typedef struct DNode{
ElemType data;
struct DNode *prior, &next;
}DNode, *DLinklist;
2.2 双链表的初始化(带头结点)
//双链表的初始化(带头结点)
bool InitDLinkList (DLinklist &L){
L = (DNode *)malloc(sizeof(DNode)); //分配一个头结点
if (L == NULL) //内存不足,分配失败
return false;
L->prior = NULL; //头结点的prior永远指向NULL
L->next = NULL; //头结点之后暂时还没有结点
return true;
}
2.3 判空——判断双链表是否为空
思想:
- 头结点的后继指针指向的是空,即双链表为空
//双链表的判空
bool Empty(DLinklist L){
if (L->next == NULL)
return ture;
else
return false;
}
2.4 双链表的插入
思想:
- 将结点s插入结点p和结点p+1之间
- s的后继指针指向p+1
- p+1的前驱指针指向s
- s的前驱指向p
- p的后继指向s
- 即先完成s和p+1的联系,再完成p和s的联系
//双链表的插入
//在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
if( p == NULL || s == NULL)
return false;
s->next = p->next; // 2
if( p->next != NULL)
p->next->prior = s; // 3
s->prior = p; // 4
p->next = s; // 5
}
注:
- 结点p和s需要判断是否合法,即是否不为空
- 当结点p为最后一个结点时,步骤3(p+1的前驱指针指向s)就需要考虑是否需要
2.5 双链表的删除
思想:
- 结点顺序分别为p、q、p+1,待删除的结点是q
- p的后继指向p+1
- p+1的前驱指向p
//双链表的删除
//删除结点p的后继结点
bool DeleteNextDNode(DNode *p){
if( p == NULL ) //判断结点p是否合法
return false;
DNode *q = p->next; //q为结点p的后继结点,即待删除的结点
if( q == NULL ) //判断结点q是否合法
return false;
p->next = q->next; //p的后继指向p+1
if( q->next != NULL ) //考虑p+1是否为空
q->next->prior = p; //若p+1不为空,再将p+1的前驱指向p
free(q); //释放删除结点的空间
return true;
}
注:
- 考虑p+1是否为空指针,再决定是否需要将p+1的前驱指向p
2.6 双链表的销毁
思想:
- 循环删除头结点的后继结点
//双链表的销毁
void DestoryList(DLinklist &L){
while ( L->next != NULL )
DeleteNextDNode(L); //循环删除头结点后的结点
free(L); //释放头结点
L=NULL; //头指针指向NULL
}
注:
- 头结点也需释放
- 头指针需指向空
3 循环链表
3.1 循环单链表
3.1.1 结点的定义
//循环单链表结点的定义
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
3.1.2 初始化
//循环单链表的初始化
bool InitList( LinkList &L) {
L = (LNode *)malloc(sizeof(LNode)); //分配头结点
if( L == NULL ) //内存不足,分配失败
return false;
L->next = L; //自己指向自己
return true;
}
3.1.3 判空
//循环单链表的判空
bool Empty( LinkList L ) {
if ( L->next == L )
return true;
else
return false;
}
3.1.4 表尾结点的判断
//判断循环单链表的表尾结点
bool isTail ( LinkList L, LNode *p ) {
if ( p->next == L ) //结点p的后继是否指向头结点
return true;
else
return false;
}
3.2 循环双链表
3.2.1 结点的定义
//循环双链表结点的定义
typedef struct DNode{
ElemType data;
struct DNode *prior, *next;
}DNode, *DLinklist;
3.2.2 初始化
//循环双链表的初始化
bool InitDLinkList( DLinklist &L) {
L = (DNode *)malloc(sizeof(DNode));
if ( L == NULL )
return false;
L->prior = L; //头结点的前驱指向头结点
L->next = L; //头结点的后继指向头结点
return true;
}
3.2.3 判空
//循环双链表的判空
bool Empty(DLinklist L) {
if( L->next == L )
return true;
else
return false;
}
3.2.4 表尾结点的判断
//判断循环双链表的表尾结点
bool isTail( DLinklist L, DNode *p) {
if ( p->next == L ) //判断结点p的后继是不是指向头结点
return true;
else
return false;
}