链表
链表:包括单链表、双链表和循环链表三种。
通过“链”建起来的表,在进行插入和删除的操作时不需要移动元素,而只需要通过修改指针,但也会失去顺序表一样的随机访问的优点
(1). 单链表
线性表的链式存储也称为单链表。包含两个部分:data数据域,存储数据;next指针域,存储指针。
单链表也分为带有头结点和不带头结点的情况
单链表结点类型:
typedef struct LNode{//单链表结点类型
int data;//数据域 ,每个结点存放一个数据元素
struct LNode *next;//指针域 ,指针指向下一个节点
}LNode,*LinkList;
typedef LNode{//单链表结点类型
int data;//数据域 ,每个结点存放一个数据元素
struct LNode *next;//指针域 ,指针指向下一个节点
};
两种结点类型定义方法是等价的
typedef struct LNode LNode;//强调这是一个结点
typedef struct LNode *LinkList; //强调这是一个单链表LinkList
//上述两个是等价的
增加一个新结点:在内存中为这个新结点申请所需空间,并用指针p指向这个结点
struct LNode *p=(struct LNode*)malloc(sizeof(struct LNode));
1. 不带头结点
单链表初始化
bool InitList(LinkList &L){
L=NULL;//头指针指向空
return true;
}
void test(){
LinkList L;
InitList(L);
}
判断单链表是否为空
bool Empty(LinkList L){
if(L==NULL)
return true;
else
return false;
}
//或:
bool Empty(LinkList L){
return(L==NULl);
}
插入(不带头结点)按位序插入
ListInsert(&L,i,e):插入操作。在表L中的第i个位置插入指定元素e。想要在i个位置插入元素,就要先找到i-1个结点,将新结点查到其后。
如果不带头结点,则插入、删除第一个元素时,需要更改头指针。
bool ListInsert(LinkList &L,int i,int e){
if(i<0)
return false;
if(i==1){
LNode *s=(LNode *)malloc(sizeof(LNode));//申请一个新的指针指向新的结点
s->data=e;//将数据赋给新结点
s->next=L;//将新指针指向L指向的结点
L=s;//将头指针指向新结点
return true;//插入元素成功
}
LNode *p;//定义一个p指针
int j=1;//当前p指向的是第几个结点
p=L;//当前p指向第一个结点(注意:不是头结点)
while(p!=NULL && j<i-1){//循环找到i-1结点
p=p->next;//p指针依次往后移动直到找到i-1结点
j++;
}
if(p==NULL)//i不合法
return false;
LNode *s=(LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
2. 带头结点
用头插法和尾插法的方法建立单链表,将输入的元素,一个个插入后形成一个完整的单链表,都只讨论带头结点的情况
头插法建立单链表:
LinkList List_HeadInsert(LinkList &L){//头插法逆向建立单链表
struct *s;
int x;
L=(LNode *)malloc(sizeof(LNode));//申请一个头结点
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;//返回头结点
}
采用头插法建立单链表,读入数据的顺序与生成的链表的数据是相反的。每个结点插入的时间为O(1),设单链表的长度为n,则总时间复杂度为O(n)。
例题:
1. 给定义一个n个元素的一维数组,建立一个有序单链表的最低时间复杂度。
方法一:直接插入排序,每插入一个元素就序遍历链表寻找插入位置,时间复杂度O(n2),
方法二:若先将数组排好序,然后建立链表,建立链表的时间复杂度O(n),
数组排序的最好时间复杂度为O(nlog2n)
尾插法建立单链表:
bool List_TailInsert(LinkList &L){
int x;
L=(LNode *)malloc(sizeof(LNode));//创造头结点
LNode *s,*r;//s指针指向要插入的新结点,r始终指向当前链表的尾结点,也叫尾指针
scanf("%d",&x);
while(x!=9999){
s=(LNode *)malloc(sizeof(LNode));//创造新结点
s->data=x;
r->next=s;
r=s;
scanf("%d",&x);
}
r->next=NULL;//尾指针置空
return L;
}
单链表初始化(带头结点):
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;s
}
按位序插入元素:
bool ListInsert(LinkList &L,int i,int e){
if(i<0)
return false;
struct *p;
int j=0;//头结点相当于位序为0的结点
p=L;
while(p!=NULL && j<i-1){
p=p->next;
p++;
}
if(p==NULL)
return false;
LNode *s=(LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
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;
s->next=p->next;
p->next=s;
return true
}
指定结点的前插操作:
bool InsertPriorNode(LNode *p,int e){//在p结点之前插入元素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->data=p->data;//将p结点中的元素赋给s结点
p->data=e;//将要插入的数据赋给p结点
return true;
}
按位序删除元素(带头结点)
ListDelete(&L,i,&e):删除操作。删除表中L中第i个位置的元素,并用e返回删除元素的值。找到第i-1个结点即要删除结点的前驱结点,删除了i位置上的元素,将指针指向第i+1个结点,并释放i个结点。
bool ListDelete(LinkList &L,int i,int e){
if(i<1)
return false;
LNode *p;
int j=0;
p=L;//L指向头结点,头结点是第0个结点(不存在数据)
while(p!=NULL && j<i-1){
p=p->next;
j++;
}
if(p==NULL)//i值不合法
return false;
if(p->next==NULL)//i-1结点后没有其他节点
return false;
LNode *q;
q=p->next;//另q指针指向要删除的结点
e=q->data; //将q结点的数据复制给变量e
p->next=q->next;
free(q);
return true;
}
删除指定结点(带结点):
bool ListDelete(LNode *p){
if(p==NULL)
return false;
LNode *q;
q=p->next;
p->data=p->next->data;//将p后继节点的数据赋给p结点,也叫交换数据域
p->next=q->next;
free(q);
return true;
按位查找:
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
LNode *GetElem(LinkList L,int i){
if(i<1)
return NULL;
int j=1;
LNode *P;
p=L->next;
while(p!=NULL && j<i){
p=p->next;
j++;
}
return p;
}
按值查找:
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
LNode *LocateElem(LinkList L,int e){
LNode *p=L->next;
while(p!=NULL && p->data!=e){
p=p->next;
return p;
}
}
补充单链表长度:
int length(LinkList L){
int len=0;//统计表长
LNode *p=L;
while(p->next!=NULL){
p=p->next;
len++;
}
return len;
}
双链表
双链表和单链表的区别:单链表结点只有一个指向后继的指针,使得单链表只能从结点顺序地向后遍历,要访问某个结点的前驱结点(插入、删除操作时),只能从头遍历,访问后继结点的时间复杂度O(1),访问前驱结点的时间复杂度O(n)。双链表为了克服上述困难,双链表结点中有两个指针prior和next指针,分别指向前驱结点和后继结点。
但双链表不可随机存取,按位查找和按值查找都只能用遍历的方式实现,时间复杂度为O(n)
双链表的插入、删除操作时间复杂度都均为O(1)
定义双链表的结点类型
typedef struct DNode{
int data;
struct DNode *prior,*next;
}DNode,*DLinkList;
初始化
bool InitList(DLinkList &L){
L=(DNode *)malloc(sizeof(DNode));
if(L==NULL)
return false;
L->prior=NULL;//头结点的前驱指针指向空
L->next=NULL;//头结点的后继指针也指向空
return true;
}
判断双链表是否为空
bool Empty(DLinkList &L){
if(L->next==NULL)
return true;
else
return false;
}
双链表的插入操作
bool InsertNextDNode(DNode *p,DNode *s,int e){
if(p==NULL||s==NULL)//指针指向空
return false;
s->data=e;
s->next=p->next;
if(p->next!=NULL)
p->next->prior=s;
s->prior=p;
p->next=s;
return true;
}
双链表的删除操作
bool DeletNextDNode(DNode *p){
DNode *q=p->next;
if(p==NULL||q==NULL)
return false;
p->next=q->next;
if(q->next!=NULL)
q->next->prior=p;
free(q);
}
双链表的遍历
//双链表向后遍历
//while(p!=NULL){
// p=p->next;
//}
//双链表向前遍历
//while(p!=NULL){
// p=p->prior;
//}
销毁双链表
void DestroyList(DLinkList &L){
while(L->next!=NULL)
DeletNextDNode(L);
free(L);
L=NULL;
}
循环链表
循环链表分为循环单链表和循环双链表
单链表:表尾结点的next指针指向NULL
循环单链表:表尾结点的next指针指向头结点,从一个结点出发可以找到其他任何一个结点
循环单链表
定义循环单链表的结点类型
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
初始化循环单链表
bool InitList(LinkList &L){
L=(LNode *)malloc(sizeof(LNode));
if(L==NULL)
return false;
L->next=L;
return true;
}
判断循环单链表是否为空
bool Empty(LinkList &L){
if(L->next==NULL)
return true;
else
return false;
}
判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,LNode *p){
if(p->next==L)
return true;
else
return false;
}
循环双链表
表头结点的prior指针指向表尾结点,表尾结点的next指针指向表头元素。
定义循环双链表的结点类型
typedef struct DNode{
int data;
struct DNode *prior,*next;
}DNode,*DLinkList;
初始化
bool InitList(DLinkList &L){
L=(DNode *)malloc(sizeof(DNode));
if(L==NULL)
return false;
L->next=L;
L->prior=L;
return true;
}
判断循环双链表是否为空
bool Empty(DLinkList &L){
if(L->next==L && L->prior=L)
return true;
else
return false;
}
判断结点p是否为循环双链表的表尾结点
bool isTail(DLinkList &L,DNode *p){
if(p->next==L)
return true;
else;
return false;
}