数据结构与算法基础(二)b线性表----链表


数据结构与算法基础(二)b线性表----链表

一、链表

1.1 链式存储结构

物理位置任意的  存储单元 来存放线性表的数据元素
存储单元可连续 可零散
逻辑次序 物理次序 不一定相同

在这里插入图片描述

1.2 概念

1.2.1结点

数据元素的存储映像,数据域、指针域两部分组成

1.2.2链表

n个结点由指针链组成一个链表
他是线性表的链式存储映像,称为线性表的链式存储结构

1.2.3 单链表

结点只有一个指针域的链表(线性链表)

在这里插入图片描述

1.2.4 双链表

结点有两个指针域的链表

在这里插入图片描述

1.2.5 循环链表

首尾相接的链表

在这里插入图片描述

1.2.6头指针 头结点 首元结点

在这里插入图片描述
头指针:指向链表中第一个结点的指针
首元结点:存储第一个数据元素的结点
头结点: 首元结点前 附加的一个结点

在这里插入图片描述

1.3 链式表的表示

1.3.1 空表的表示

无头结点时,头指针为空时
有头结点时,头结点的指针域为空

无头结点
有头结点

1.3.2 头结点的好处

便于处理首元结点
便于空表和非空表的统一处理:无论链表是否非空,头指针都是指向头结点的非空指针,因此空表和非空表的处理也就统一了

1.3.3 头结点 的数据域中装什么?

可为空,可存放线性表的长度等附加信息

在这里插入图片描述

1.4 链表的特点

  • 结点在存储器中的位置是任意的,逻辑和物理不一定相邻
  • 访问时只能通过头指针进入链表,顺着每个节点的指针域依次向后扫描其余结点,寻找第一个结点和最后一个结点所花费的时间不等
  • 链表—顺序存取法
  • 顺序表----随机存取

二、单链表

## 2.1 定义
单链表 由表头唯一确定,因此单链表可以用头指针的名字命名, 头指针名为L,则链表称为L

在这里插入图片描述

2.2 单链表的存储结构

数据域,指针域
在这里插入图片描述

嵌套的定义,指针又指向一个包含两个元素的结构体
Lnode表示结点的结构体   Lnode a; a.data    a.next
定义链表L: LinkList L;
定义结点指针p: LNode*p    <----->LinkList p;

2.3 举例

在这里插入图片描述

在这里插入图片描述

2.4 单链表的基本操作

2.4.1 单链表的初始化

构造一个空表
- 开辟一块儿内存,生成新结点 作为头结点,用头指针指向头结点
- 头结点的指针域置空

算法描述:

Status InitiList_L(LinkList &L){
	L=new LNode;//new一个结点,new的结果是指向结点的指针,赋值给L
	//或 L=(LinkList)malloc(sizeof(Lnode));给Lnode的长度分配一块空间,转换成LinkList的地址
	L->next=Null;//L的下一个域 置空
	return OK;
}

2.4.2 判断链表是否为空

思路:头结点的指针域是否为空

int ListEmpty(LinkList L){//传入一个链表L,判断若是空表返回1
	if(L->next)  //头指针有数据域和指针域,判断指针域的情况;非空
		return 0;
	else 
		return 1;
}

2.4.3 单链表的销毁:链表销毁后不存在

思路:从头结点开始,依次释放每一个结点
在这里插入图片描述

L是这个链表  需要一个额外的指针p
指针p指向头指针L-------p中存储L的地址
p=L 将L赋值给p
L指向下一个结点----下一个节点的地址赋值给L:L=L->next;
现在就可把p指向的结点删除 free(p);(c)       delete p;  (c++)
什么时候结束,当L指向空
结束条件:L==NULL
循环条件:L!=NULL   或 L

在这里插入图片描述

Status DestroyList_L(LinkList &L){//销毁单链表L
	Lnode *p;//或LinkList P;//当前想要销毁的结点
	while(L){//当前结点不为空
		p=L;
		L=L->next;
		delete p;
	}
	return OK;
}

2.4.4 清空链表

链表仍存在,单链表中无元素,成为空链表(头指针和头结点热然存在)
思路: 依次释放所有结点,并将头结点的指针域设置为空
在这里插入图片描述

L 头结点
a1 首元结点
L->next    L指向首元结点的指针域,并赋值给p
p指向首元结点 
(回忆上一个算法要连同头结点一起删除,p=L从第一个开始
从第二个开始p=L->next; )
想要删除p但是就无法定位a2,则q指向a2的指针域,就可以删除p了;
p=q;    q赋值给p,q再指向下一个指针域

在这里插入图片描述

Status ClearList(LinkList &L){//将L重置为空表
	Lnode *p,*q; //或LinkList p,q;p用来存放要删除的结点,q存放下一个结点
	p=L->next;
	while(p){    //没到表尾
	q=p->next;
	delete p;
	p=q;
	}
    L->next=Null;  //头结点指针域为空
    return OK;
}

2.4.5 求单链表的表长

思路:从首元结点开始依次计数所有的结点

基本操作:p=p->next
int ListLength_L(LinkList L){//返回L中数据元素的个数
	LinkList p;  //或LNode *p  指向单链表中一个结点的指针变量
	p=L->next; //L指向头结点的指针域   p指向第一个结点
	i=0;
	while(p){  //遍历单链表,统计结点数
		i++;
		p=p->next;
	}
	return i;
}

回顾
在这里插入图片描述

2.4.6 取值 取单链表中第i个元素的内容

思考:顺序表中如何找到第i个元素? 找到elem[i-1]
在这里插入图片描述

-从链表的头指针出发,顺着链域next逐个结点向下搜索,知道搜索到第i个结点为止,链表不是随机存取结构
-算法:

在这里插入图片描述

Status GetElem_L(LinkList L,int i,ElemType &e){//从链表L中获取第i个元素的值,由变量e返回 ; &e引用型变量
	p=L->next; j=1; //初始化 指针指向首元结点,计数器在第一个结点
	while(p&&j<i){//向后扫描,直到p指向第i个元素或p为空
	p=p->next;++j;
	}
	if(!p||j>i) return ERROR;//取第i个元素不存在
	e=p->data;//取第i个元素
	return OK;

}

2.4.7 按值查找

–根据指定数据获取该数据所在的位置(地址)
在这里插入图片描述
算法步骤

Lnode *LocateElem_L(LinkList L,Elemtype e){
	//在线性表L中查找值为e的数据元素
	//找到,则返回L中值为e的数据元素的地址,查找失败则返回NULL
	p=L->next;//p指向首元结点
	while(p&&p->data!=e)
		p=p->next;//执行次数最多,最好只用1次,做多n次,所以O(n) 
	return p;
}

–按值查找,找到后返回该数据的位置序号

//在线性表L中查找值为e的数据元素的位置序号
int LocateElem_L(LinlList L,Elemtype e){//返回L中值为e的数据元素的位置序号,查找失败返回0
p=->next;j=1;
while(p&&p->data!=e)//指针不为空且还没有找到
	{p=p->next;j++;}
if(p) return j;//
else return 0;

}

2.4.8 插入

在这里插入图片描述
在这里插入图片描述
不可先2后1,ai的地址会丢失

Status ListInsert_L(LinkList &L,int i,ElemType e){
	//寻找i-1个结点,p指向i-1结点
	p=L;j=0;
	while(p&&j<i-1){p=p->next;++j;}
if(!p||j>i-1)return ERROR;//i大于 表长+1 或者小于1,插入位置非法
s=new LNode; s->data=e;//生成新结点s,将结点s的数据域置为e
//关键,先后继再前趋
s->next=p->next;
p->next=s;
}
return OK;

2.4.9 删除

在这里插入图片描述
在这里插入图片描述

Status ListDelete_L(LinkList &L,int i, ELemtype &e){//链表L,要删除的位置i,删除的结点可通过e保存
	p=L;j=0;
	while(p->next&&j<i-1){p=p->next;++j;}//找到要删除结点i的前驱i-1,指针变量p指向i-1

	if(!(p->next)||j>i-1) return ERROR;
	q=p->next;//p为i-1个结点,q为第i个结点,临时保存被删结点i
	p->next=q->next;//修改被删结点前驱结点的指针域
	e=q->data;//保存第i个结点的数据
	delete q;
return OK;
}

插入和删除的时间复杂度:
因为线性链表不需要移动元素,只要修改指针,一般情况下时间复杂度为O(1)
但由于要从头查找前驱结点,所消耗的时间复杂度为O(n)

2.4.10单链表的建立

  • 头插法:元素插在链表的头部

在这里插入图片描述

在这里插入图片描述

void CreateList_H(LinkList &L,int n){//插入n次,循环n次
	L=new LNode;//生成头结点
	L->next=NULL;//头结点的指针域置空
	for(i=n;i>0;i--){
		p=new LNode;//生成新结点p=(LNode)*malloc(sizeof(LNode));
		cin>>p->data;//输入元素值,放在data域  scanf(&p->data)
		p->next=L->next;//  原来的首元结点L->next 的地址赋值给新结点的地址域。即新结点插到表头
		L->next=p; //将新结点接在链表头结点
	}
	
}

头插法的时间复杂度为O(n)

  • 尾插法: 元素插在链表尾部,后插法
    在这里插入图片描述
void createList_R(LinkList &L,int n){//引用型
	L=new LNode;//开辟一块新内存为头结点,  L头指针
	L->next=NULL;//给头结点的next域赋值为空
	r=L;//此时尾指针也指向头节点,将头指针赋值给尾指针
	for(i=0;i<n;++i){//插入n个结点,执行n次
		p=new LNode;//生成新结点
		cin>>p->data;//输入元素值
		p->next=NULL;//置空

        r->next=p;// 将新结点赋值给尾结点;即插入到表尾
        r=p; //改变尾指针,尾指针指向新结点
	}
}

算法的时间复杂度为O(n)

三、循环链表

3.1 定义

一种头尾相接的链表
表中最后一个节点的指针域指向头结点,整个链表形成一个环

在这里插入图片描述

优点:从表中任意出发均可以找到表中其他结点

3.2 结束条件的判断

在这里插入图片描述
在这里插入图片描述

3.3 循环链表的合并

在这里插入图片描述
上述操作分别对应:

  • p=Ta->next;
  • Ta->next=Tb->next->next;// Tb->next是b1的地址
  • delete Tb->next;
  • Tb->next=p;
LinkList Connect(LinkList Ta,LinkList Tb){//假设 ta tb都是非空的单循环链表
	p=Ta->next;//1.p存头结点
	Ta->next=Tb->next->next;//2. tb表头连结ta表尾
	delete Tb->next;//3. 释放tb的表头结点
	Tb->next=p;//4.修改指针
	return Tb;
}

时间复杂度:
每个语句都执行一次,共四次,O(1)

四、双向链表

在这里插入图片描述

4.1 双向链表的结构

在这里插入图片描述

4.2 双向循环链表

在这里插入图片描述

4.2.1 双向链表的插入

在这里插入图片描述

1. x的前驱要存a的地址;a的地址存在b的prior域里
2. a的next域要存x的地址;a的next是p的前驱的后继
3. x的next域里要存b的地址
4. b的前驱要存x的地址
Void LinkInsert_DuL(DuLinkList&L,int i,ElemType e){//在带头结点的双向循环链表L中的第i个位置之前插入元素e
	if(!(p=GetElemP_DuL(L,i))) return ERROR;
	s=new DuLNode;
	s->date=e;
	s->prior=p->prior;//1
	p->prior->next=s;//2
	s->next=p;//3
	p->prior=s;//4
	return OK;
}//ListInsert_DuL

4.2.2 双向链表的删除

在这里插入图片描述

1. a的next域要存c的地址; c的地址存在b的next域里
2. c的prior域里要存a的地址;a的地址存在b的prior域里
Void LinkDelete_DuL(DuLinkList&L,int i,ElemType &e){//删除带头结点的双向循环链表L中的第i个元素并用e返回
	if(!(p=GetElemP_DuL(L,i))) return ERROR;
	e=p->data;
	p->prior->next=p->next;//1
	p->next->prior=p->prior;//2
	free(p);//释放
	return OK;
}//ListDelete_DuL

4.3 单链表、循环链表和双向链表的

在这里插入图片描述

五、 顺序表和链表的比较

在这里插入图片描述

在这里插入图片描述

六、应用举例

6.1线性表的应用

在这里插入图片描述

6.1.1 线性表的合并:求并集

  • 算法步骤: 依次取出Lb中的元素,在La中查找该元素,如果找不到,则将其插在La的最后
Void union(List &La,List Lb){
   La_len=ListLength(La);
   Lb_len=ListLength(Lb);
   for(i=1;i<=Lb_len;i++){
   	GetElem(Lb,i,e);
   	if(!LocateElem(La,e))   ListInsert(&La,++La_len,e)
   }
}
}//ListDelete_DuL

算法的时间复杂度是: O(ListLength(La)*ListLength(Lb))

6.1.2 有序表的合并

算法步骤:
1创建一个空表Lc
2依次从La Lb中摘取元素值较小的结点插在Lc的最后,直至其中一个变为空表为止
3继续将剩余表的剩余结点插在Lc表的最后

用顺序表

Void MergeList_Sq(SqList LA,SqList LB,SqList &LC){
   pa=LA.elem;
   pb=LB.elem;// 两个指针的初值分别指向两个表中的第一个元素
   LC.length=LA.length+LB.length;//新表长度为两个表的长度之和
   LC.elem=new Elemtype[LC.length];//为新表分配一个数组空间
   pc=LC.elem;//指针pc指向新表的第一个元素
   pa_last=LA.elem+LA.length-1;//指针pa_last指向LA表的最后一个元素
    pb_last=LB.elem+LB.length-1;//指针pb_last指向LB表的最后一个元素


while(pa<=pa_last&&pb<=pb_last){//两个表都非空
   if(*pa<=*pb) *pc++=*pa++; //依次摘取两表中较小的结点
   	else *pc++=*pb++;
}
   while(pa<pa_last) *pc++=*pa++;  // LB表已达到表尾,将LA中剩余元素加入LC
   while(pb<=pb_last) *pc++=*pb++;//LA表已经达到表尾,将LB中剩余元素加入LC

}//MergeList_Sq

算法的时间和空间复杂度都是:O(ListLength(La)+ListLength(Lb))

用链表实现有序表的合并

在这里插入图片描述

Void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc){//Lc是合并后的链表
  pa=La->next;
  pb=Lb->next;
  pc=Lc=La;//用La的头结点作为Lc的头结点
  while(pa&&pb){
  	if(pa->data<=pb->data){//比较pa pb指针data域的值
  		pc->next=pa;
  		pc=pa; 
  		pa=pa->next;}
  	else{pc->next=pb; pc=pb;pb=pb->next;}
  }
  pc->next=pa?pa:pb;//插入剩余段,pa非空,则加入pa否则pb
  delete Lb;//释放Lb的头结点

}
}//ListDelete_DuL

时间复杂度:最坏的情况 pa pb都要访问一次
O(ListLength(La)+ListLength(Lb))
空间复杂度:就在原来链表的基础上O(1)

6.2 案例分析

6.2.1 一元多项式的运算 加减乘运算

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

多项式相加的步骤:

void CreatePolyn(Polynumial &p,int n){//建立表示多项式的有序链表p,输入m项的系数和指数
	p->new PNode;//建立一个带头结点的单链表
	p->next=NULL;
	for(i=1;i<=n;++i){//依次输入n个非零项
		s=new PNode;//生成新结点
		cin>>s->coef>>s->expn;//输入系数和指数
		pre=p;//pre保存q的前驱,初值为头结点
		q=P->next;//q初始化。指向首元结点
		while(q&&q->expn<s->expn){//找到第一个大于输入指数的项*q
			pre=q;
			q=q->next;
		}
		s->next=q;//将输入项s插刀q和其前驱结点pre之间
		pre->next=s;
	}
}

6.2.2图书管理系统

在这里插入图片描述

若用顺序表:数组的每一个元素都是一个复合类型,包含有三个部分
若用链式结构:每个数据元素的data域都是由三个部分构成的结构类型
根据实际:通过序号查找:顺序;经常插入删除运算,链式
struct Book{
	char id[20];
	char name[50];
	int price;
}


//顺序表
typedef struct{
	Book *elem;
	int length;
}SqList;

//链表
typedef struct LNode{
	Book data;
	struct LNode *next;
}LNode,*LinkList;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值