数据结构第二章(单链表部分)学习笔记

本文是关于数据结构中线性表的单链表部分的学习笔记,介绍了单链表的定义、存储结构,包括带头结点和不带头结点的形态。详细阐述了单链表的插入、删除、查找等基本操作,以及头插法、尾插法创建单链表的算法步骤。还探讨了循环链表和双向链表的概念,以及如何在循环链表中进行合并操作。

线性表---单链表部分

1、知识承接

顺序表可以随时存取表中的任意一个元素,它的存储位置可以用一个简单直观的公式来表示。但效率低插入和删除操作需要移动大量的元素。临时扩大存储空间有困难存储分配只能预先进行必须占用地址连续的内存空间 .

链式存储线性表时,不需要使用地址连续的存储单元,即不要求逻辑上相邻的元素在物理位置上也相邻,它通过“链”建立起数据元素之间的逻辑关系,因此插入和删除操作不需要移动大量的元素,只需要修改指针,但也会失去顺序表可以随机存取的优点。

单链表

链表:

通过一组任意的存储单元来存储线性表中的数据元素,存储单元可以连续,也可以不连续

1)单链表的定义

线性表的链式存储又称单链表,它是通过一组任意的存储单元来存储线性表中的数据元素,为了建立数据元素之间的线性关系,对每个链表结点除了存放元素自身的信息外,还需要存放一个指向其后继的指针。图示如下:

其中data为数据域,存放数据元素next为指针域,存放其后继结点的地址。

代码定义

/******单链表定义*******
为了提高程序可读性,对同一结构体指针类型起了两个名称。LinkList和Lnode本质上是等价的 
习惯用LinkList定义单链表,强调定义的是某个单链表的头指针;
用LNode *定义指向单链表中任意结点的指针变量 
申请空间:p = new LNode;                        //C++实现 
          p = (LNode *)malloc(sizeof(LNode));   //C语言实现  
**********************/

#include <iostream>

typedef int ElemType;

using namespace std;

typedef struct LNode          //定义结点类型 
{
	ElemType data;            //数据域,每个结点存放一个数据元素 

	struct LNode *next;       //指针域,指针指向下一个结点,其类型为指向结点的指针类型LNode *; 

}LNode, *LinkList;            //LinkList为指向结构体LNode的指针类型

申请空间:

p = new LNode;                        //C++实现

p = (LNode *)malloc(sizeof(LNode));   //c语言实现

相关概念说明

首元结点:链表中存储第一个数据元素a1的结点

头结点:一般情况,为了处理方便,在单链表的第一个结点之前附设一个结点,其指针域指向首元结点。

头指针:指向链表中第一个结点的指针

2)单链表的两种形式

 带头结点的和不带头结点,如下:

带头结点:


//初始化一个单链表带头结点 
bool InitList(LinkList &L)
{
	L = new LNode;             //分配一个头结点

	if (L == NULL)             //内存不足,分配失败
	    return false;

	L->next = NULL;            //头结点之后暂时没有结点,将结点指针域置空
	return true; 
}

不带头结点:

//初始化不带头结点

bool InitList(LinkList &L)

{

     L = NULL              //空表,暂时没有任何结点(防止脏数据)

     return true;

 }

 比较:带头节点的好处,头指针L永远指向头结点不用修改(插入删除代码不带头结点时,由于L指向首元结点,所以插入第一个结点和删除最后一个结点时都需要修改L(代码会多一个条件处理)。

     

 结点类型的别名

    

单链表的基本操作(没有特别说明,默认都带头结点)

单链表的初始化

算法步骤:

1、生成新结点为头结点,用头指针指向头结点

2、头结点指针域置空

//初始化

bool InitList(LinkList &L)

{  //构造一个空的单链表L

    L = new LNode;               //生成新结点为头结点,用头指针指向头结点

    L->next = NULL;              //头结点指针域置空

    return true;

 }

单链表的判空操作

//判断是否为空表

bool Empty(LinkList L)

{

    return (L == NULL);

// 或者

//    if (L == NULL)

//            return true;

//        else

//            return false;

 }

单链表求表长

//表长
int Length(LinkList L)
{
	int len = 0;
	LNode *p = L;

	while (!p->next)              
	{
		p = p->next;
		len++;
	}

	return len;
}

单链表的取值

算法步骤

1、用指针变量p指向首元结点,用j作为计数器初值赋为1

2、从首元结点开始依次顺着链域next往下访问只要指向当前结点的指针p不为空, 并且没有到达序号为i的结点,则循环执行以下操作:P指向下一个结点j相应+1

3、退出循环时,如果指针P为空,或者计数器j>i,说明指定的序号i值不合法(i > 表长 || i <= 0),取值失败返回error;否则取值成功,此时j = i时,p所指的结点就是要找的第i个结点,用参数e保存当前结点的数据域,返回Ok

//单链表的取值 

 
bool GetElem(LinkList L, int i, ElemType &e)
{  //在带头结点的单链表L中根据序号i获取元素的值,用e返回L中的第i个数据元素的值 

    LNode *p;

	p = L->next;               //初始化,P指向首元结点,计数器j初始值为1 

	int j = 1;

	while(p && j < i)          //顺链域向后扫描,直到P为空或者p指向第i个数据元素 
	{
		p = p->next;           //p指向下一个结点 
		j++;                   //计数器加1 
	}

	if(!p || j > i)            //如果p为空或者i的值不合法,i > n或者 i < 0 
		return false;   

	e = p->data;               //将第i个结点的数据域赋给e,带回 

	return true;
 } 

单链表的按值查找

LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。

算法步骤:

1、用指针p指向首元结点;

2、从首元结点开始依此顺着链域next向下查找,只要指向当前结点的指针p不为空,并且p所指向的结点的数据域不等于给定值e,则循环执行以下操作:p指向下一个结点

3、返回p。若查找成功,p此时为结点的地址值,若查找失败,p的值即为NULL。


//按值查找 

LNode *LocateElem(LinkList L, ElemType e)
{  //在带头结点的单链表L下查找值为e的元素 

	LNode *p;

	p = L->next;                    //初始化p指向首元结点 

	while (p && p->data != e)       //如果p不为空且p所指向的数据域不等于e 
		p = p->next;                //p指向下一个结点 

	return p;                       //查找成功返回e的结点地址p,查找失败p为NULL 
} 

单链表按位查找

GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。在第i个位置插入元素e

算法步骤:

1、用指针p指向首元结点

2、判断要查找元素的位置,如果要查找的元素在第一个,则直接返回L头结点;如果要查找的元素小于1,则位置不合法,返回空值;如果位置合法且p不为空的话,则p指向下一节点的地址,即p = p->next;查找成功返回p.

//按位查找,返回第i个元素(带头结点) 

LNode *GetElem(LinkList L, int i)
{
	int j = 1;        
             
    LNode *p = L->next;            //定义一个指针P,指向首元结点
 
	if (i == 0)                    //如果i的值为0,说明要查在的元素是头结点 
	   return L;

	if (i < 1)                     //i < 1位置不合法,返回NULL 
	   return NULL;

	if (j < i)                     //j < i,p就顺着链域向下找 
	{
		p = p->next;
	    j++;
	}

	return p;
}

//按位查找
Status GetElem(LinkList L, int i)
{

	int j = 1;
	LNode *p = L->next;

	while (p && j < i)
	{
		p = p->next;
		j++;
	}

	if (!p || j > i)
	    return error;

	return p;
}

单链表的插入

ListInsert(&L,i,e):插入操作。在表L中的第i个位置(找到第 i-1 个结点,将新结点插入其后)上插入指定元素e。

带头结点:头结点可以看作“第0个”结点

算法步骤:

1、首先找到ai-1个结点(也就是要插入结点的前驱结点),p指向该结点;

2、生成一个新结点*s;

3、将新结点的数据域置为e

4、将新结点*s的指针域指向结点ai

5、将结点*p的指针域指向新结点s

//插入算法,将元素e插入到第i个位置上 (带头结点)
Status ListInsert(LinkList &L, int i, ElemType e)
{
	LNode *p;

	p = L;                        //p指向头结点 
	int j = 0;                    //记录当前p指向第几个结点,L是头结点,第0个,不存放数据

	while (p && j < i - 1)        //循环到j-1个结点 
	{
		p = p->next;
		j++;
	} 

	if (!p || j > i - 1)
	    return error; 

	LNode *s;
	s = new LNode;               //new一个新结点用来存放要插入的元素,其数据域为e 
	s->data = e;                 //新结点的数据域置为e
 
	s->next = p->next;           //新结点的指针域指向ai
	p->next = s;                 //结点ai-1的指针域指向新结点 

	return ok;
} 

不带头结点

如果不带头结点,则插入、删除第1个元素时,需要更改头指针L

不存在 “第0个”结点,因此 i=1 时需要特殊处理

//插入算法,(不带头结点)
//不存在第0个结点,因此i = 1要做特殊处理 
Status ListInsert (LinkList &L, int i, ElemType e)
{
    if (i < 1)
	    return error;

	if (i == 1)                   //插入第一个结点与其他结点的操作不同 
	{
		LNode *s = new LNode;
		s->data = e; 
		s->next = L;
		L = s;                    //头指针指向新结点
		return ok; 
	}

	LNode *p;                     //P指向扫描到当前的结点 
	int j = 1;                    //p指向的是第几个结点
	p = L;                        //p指向第一个结点(注意:不是头结点哦)
 
	while (!p && j < i - 1)
	{
		p = p->next;
		j++;
	}

	if (!p)
	   return error;              //i值不合法,返回error

	LNode *s = new LNode;
	s->data = e;                  //新结点数据域置为e 

	s->next = p->next;            //将ai结点的地址放到新结点的next域 
	p->next = s;                  //将新结点的地址放到ai-1的next域 

	return ok; 
}

单链表的删除:

ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。

算法步骤:

1、查找结点ai-1(要删除结点的前驱结点),并由指针p指向该结点

2、临时保存待删除结点a1的地址在q中,以备释放

3、将结点*p的指针域指向ai的直接后继结点

4、释放结点ai的空间。

//删除
Status ListDelete(LinkList &L, int i, ElemType &e)
{
	LNode *p, *q;                 //p指向当前扫描的结点
 
	int j = 0;                    //记录当前结点p指向第几个结点
 
	p = L;                        //L指向头结点,第0个结点(不存数据)
 
	while (p->next && j < i- 1)   //循环找到删除结点的前驱结点,即第i-1个结点 
	{
		p = p->next;
		j++;
	}

	if (!p->next || j > i - 1)   //位置不合法 
	   return error;

	q = p->next;                 //q指向被删除的结点 
	p->next = q->next;           //将*q结点从链中断开 

	e = q->data;                 //用e返回元素的值 
	delete q;                    //释放结点空间 

	return ok;
}

单链表---头插法创建单链表

元素插在链表的头部

链表的结点链接顺序与逻辑次序相反

算法步骤:

1、创建一个只有头结点的空链表

2、根据待创建链表包括的元素个数n,循环执行以下操作:

  • 生成一个新结点*p;
  • 输入元素值赋给新结点*p的数据域
  • 将新结点*p插入到头结点之后
//头插法
void CreatList_H(LinkList &L, int n)
{

	L = new LNode ;
	L->next = NULL;                   //建立一个带头结点的单链表 

	for (int i = 0; i < n; i++)  
	{
		LNode *p = new LNode;         //生成新结点*p 

		cin >> p->data;               //输入元素值赋给新结点的数据域 
		
		//将新结点插入到头结点之后

		p->next = L->next;             
		L->next = p;
	}
 } 

单链表---尾插法创建单链表

元素插在链表的尾部

链表的结点链接顺序与逻辑次序相同

算法步骤:

1、创建一个只有头结点的空链表

2、尾指针 r 初始化,指向头结点

3、根据创建链表包括的元素个数n,循环n次执行以下操作:

  • 生成一个新结点*p;
  • 输入元素值赋给新结点*p的数据域;
  • 将新结点*p插入到尾结点*r之后;
  • 尾指针 r 指向新的尾结点*p
//尾插法
void CreatList_R(LinkList &L, int n)
{

	L = new LNode ;
	L->next = NULL;                   //创建一个到头结点的空链表 

	LNode *r = L;                    //尾指针指向头结点

	for (int i = 0; i < n; i++)
	{

		LNode *p = new LNode;         //生成新结点 
		cin >> p->data;               //输入元素值赋给新结点*p的数据域 
		
		//将新结点*p插入到尾结点*r之后 

		p->next = NULL;               
		r->next = p;
		
		r = p;                        //r指向新的尾结点*p 
	 } 
 } 

单链表的输出

//输出
Status DispLsit(LinkList L)
{
	LNode *p;
	p = L->next;

	while (p)
	{
		cout << p->data << ' ';
		p = p->next;
	}

	cout << endl;
	return ok;
 } 

循环链表---单循环链表

循环链表

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

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

注意:

由于循环链表中没有空指针,所以涉及到在遍历操作时,其终止条件就不再像非循环链表那样判断 p 或者 p->next是否为空,而是判断它们是否等于头指针。

p! = L

p->next != L

头指针表示循环链表时(不方便)

a1的时间复杂度是O(1)

an的时间复杂度是O(n)

尾指针表示循环链表时(相对方便一些)

a1的时间复杂度是O(1)   -----------a1的存储位置也就是 r->next->next 

an的时间复杂度是O(1)  -----------an的存储位置是 r

两个仅有尾指针的循环链表的合并

将其中一个链表接在另一个链表的表尾,例如,把下面的链表接在上面链表的表尾

1、首先我们可以把La这个链表的头指针找到,在循环链表中,尾指针的next域所指向的就是头指针的地址,也就是 p = ra->next ,p指向头结点

2、然后将Lb链表接上去,此时就可以直接Lb的首元结点接在La链表的尾指针后面,Lb的首元结点的地址存放在头结点的next域中,而头结点的地址又存放在尾结点的next域中,所以要找Lb链表中的首元结点,就得一层层的向前推,即 ra->next = rb->next->next

3、将Lb链表中的头结点存放在Lb链表的尾指针的next域中。即 rb->next = p

/*带尾指针的循环链表合并*/

LinkList Connect(LinkList Ta, LinkList Tb)
{  
    //假设Ta,Tb都非空 
	p = Ta->next;                   //p存放头结点地址  

	Ta->next = Tb->next->next;      //Tb表头连Ta的表尾 

	delete Tb->next;                //释放Tb头结点 

	Tb->next = p;                   //修改指针 

	return Tb;
}

双向链表

双向链表的每个结点内有两个指针域,一个指向其直接前驱,一个指向其直接后继

双向链表插入算法

在带头结点的双向链表L中的第i个位置之前插入元素e

插入语句步骤:

  • s->prior=p->prior
  • p->prior->next=s
  • s->next=p
  • p->prior=s
//插入,在带头结点的双向链表L中的第i个位置之前插入元素e 
Status ListInsert_D(DListLink &L , int i, ElemType &e)
{

	if (!(p = GetElem_D(L, i )))             //表为空的话,返回error 
	    return error;

	DNode *s = new DNode;                    //new一个新结点s 

	s->data = e;                             //将元素e存放在新结点的数据域
 
	s->prior = p->prior;                     //将第ai-1个结点的地址存放在新结点s的prior中 

	p->prior->next = s;                      //将新结点s的地址存放在第ai-1个结点的next

	s->next = p;                             //将第i个结点的地址存放在新结点s的next域中 

	p->prior = s;                            //将新结点s的地址存放在第ai个结点的prior中 

	return ok; 
}



//详细版,其实都差不多,上面只不过是调用了取值算法
//插入,在带头结点的双向链表L中的第i个位置之前插入元素e 

Status ListInsert_D(DLinkList &L , int i, ElemType &e)
{
	DNode *p = L;                           //p指向头结点 
	int j = 0;
	while (p && j < i - 1)
	{
		p = p->next;                        //找到要删除结点的前驱结点 
		j++;
	}
	
	if (!p|| j > i - 1)
	    return error; 
	    
	DNode *s = new DNode;                    //new一个新结点s 
	s->data = e;                             //将元素e存放在新结点的数据域 
	
	s->prior = p->prior;                     //将第ai-1个结点的地址存放在新结点s的prior中 
	p->prior->next = s;                     //将新结点s的地址存放在第ai-1个结点的next域中
	s->next = p;                             //将第i个结点的地址存放在新结点s的next域中 
	p->prior = s;                            //将新结点s的地址存放在第ai个结点的prior中 
	
	return ok; 
}

双向链表的删除算法

删除算法语句

p->prior->next = p->next;               

p->next->prior = p->prior;

//删除,删除带头结点的双向链表L中的第i个元素,并用e返回 
Status LsitDelete_D(DLinkList &L, int i, ElemType &e)
{

	if (!(p = GetElem_D(L, i)))
	    return error;

	DNode *p ;
	e = p->data;                             //e保存要删除结点的数据域 

	p->prior->next = p->next;                //将ai+1个结点的地址存放在ai-1结点的next域中 

	p->next->prior = p->prior;               //将ai-1结点的地址存放在ai+1结点的prior域中 

	free(p);                                 //释放结点

	return ok;
 }


//上下都差不多
Status LsitDelete_D(DLinkList &L, int i, ElemType &e)
{
	DNode *p = L;                           //p指向头结点 
	int j = 0; 
	while (p->next && j < i-1)
	{
		p = p->next;
		j++;
	}
	
	if (!(p->next) || j > i - 1)
	   return error;

	e = p->data;                             //e保存要删除结点的数据域 
	p->prior->next = p->next;                //将ai+1个结点的地址存放在ai-1结点的next域中 
	p->next->prior = p->prior;               //将ai-1结点的地址存放在ai+1结点的prior域中 
	
	free(p);                                 //释放结点 
	
	return ok;
 }  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值