数据结构之单链表的基本运算

这一篇博文我将要记录单链表的基本运算。单链表的基本运算包括以下几个部分:

  • 初始化
  • 创建单链表
  • 取值
  • 查找
  • 插入
  • 删除

链表(Linked List)是我们常用的一种的存储数据的方式,它的基本特点就是每个数据单元在内存中都是无序存在的(常常是无序的,也可以是连续的)。相反的,数据单元有序存在的表呢我们称为顺序表(Sequential List)。
链表示意图

我们学习过数组。链表和数组类似(实际上数组跟顺序表类似),都有一个一个的数据单元,在每一个单元里面我们可以存放数据,比如整型(int)的、浮点(float)的、或者字符(char)等等。

由于链表中的元素无序,意思就是说,我可以找到现在这个数据,但是我无法知道下一个数据在哪里,更不可能知道上一个数据在哪里。这一点就是和顺序表的区别。为了表示链表中的每一个数据元素(也就是数据单元)与其直接后继数据元素之间的逻辑关系呢,我们就给每一个单元后面统一贴上一个“标签”来记录下一个单元的地址在哪里。这两部分信息组成一个数据元素,我们称为结点(node)。它包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继地址的那个“标签”称为指针域。由于每一个结点中的指针域只是指向下一个结点的地址,所以访问结点的时候是单向的,故又称线性链表单链表。(具体的可以在任何一本讲数据结构的书中找到)

下面是单链表的存储结构的C语言描述:

//-------单链表的存储结构-----------
typedef struct LNode
{
	ElemType data;                 //结点的数据域
	struct LNode  *next;          //结点的指针域
}LNode,*LinkList;                //LinkList为指向结构体LNode的指针类型

上面是很多教材给结点的定义,最初我其实还是不能理解,那鬼什么的ElemType是什么鬼,那我给两个例子详细解释一下。

例子一:

//-------单链表的存储结构-----------
typedef struct ListNode
{
	int num;                 //结点的数据域
	struct ListNode  *next;          //结点的指针域
}ListNode,*LinkList;          //LinkList为指向结构体ListNo类的指针类型

例子二:

//-------单链表的存储结构-----------
typedef struct jiedian
{
	char letter;                 //结点的数据域
	struct jiedian  *next;          //结点的指针域
}jiedian,*LinkList;                //LinkList为指向结构体jieidan的指针类型

“为了提高程序的可读性,再次对同一结构体指针类型起了两个名称,LinkList与LNode*,在本质上是等价的。通常习惯上用LinkList定义单链表,强调定义的是某个单链表的头指针,若定义LNode *定义指向单链表中任意结点的指针变量。"
这是教材上对上述结构体的解释,什么意思呢?
就是我们在使用单链表的时候,常常会定义两到三个指针。
其中一个指向头结点的指针我们称为“头指针”,常常是在“初始化单链表”的时候用到,一般情况下不移动,表示我们已经有了一个单链表,位置在这个头指针指向的地方,表的名字一般是头指针的名字。
另外两个指针呢一般指向表中的结点,用于访问结点,经常移动。(在后面的实例中会集中体现)

这里还要区别一下头指针、头结点和首元结点之间的“恩怨纠葛”。如上图“链表示意图”,头指针指向的就是头结点。
一般情况下,链表中的第一个结点不放数据,一来可以作为“监视哨”,二来该结点可以放后面结点的个数(这样统计结点个数的时候更加方便,这种算法更优秀。)这个结点就是头结点。头肯定就是第一个嘛。
头结点之后的那个结点就是首元结点了,真正存放有效数据的第一个结点称为首元结点。


单链表基本操作的实现

单链表的初始化

直接上代码:

//初始化,构造一个空的单链表L
status InitList(LinkList &L)
{
	L = new LNode;//L指向新的结点
	L->next = NULL;//头结点的指针域指空
	return OK;
}

创建单链表

上一步中初始化了一个单链表还只是一个空表,创建表呢就是向这个空表中加入结点并填充数据。

这里呢创建单链表有2种方式,一种是前插法,一种是后插法。

前插法:新建的结点均放在表的前端,即成为头结点,越先创建的结点越靠后。

void CreatList_H(LinkList &L,int n)
{//逆序输入n个元素的值,默认单链表已经初始化
	LinkList p;//操作结点的指针
	int i;//计数器
	
	for( i=0 ; i<n ; i++)//
	{
	 p = new LNode;
	 cin>>p->data;
	 p->next = L->next;//这两步就是在插入新结点
	 L->next = p;
	 }
}

后插法:新建的结点均放在表的末端,即成为尾结点,越后创建的结点越靠后。后插法更符合我们习惯的逻辑。

void CreatList_R(LinkList &L,int n)
{//正序输入n个元素的值,默认单链表已经初始化
	LinkList r,p;//定义两个指针操作结点
	int i;
	r = L;
	for(i = 0; i<n ; i++)
	{
	p = new LNode;
	cin>>p->data;
	p->next = NULL;
	r->next = p;
	r = p;
	}
}

取值
由于单链表的结点处于无序的状态,无法像数组或者顺序表那样直接找到某个结点,所以必须从链表的首元结点开始,沿着指针域逐个向下访问。

status GetElem(LinkList &L,int i,ElemType &e)
{//在带头结点的单链表L中根据序号i获取元素的值,用e返回L中第i个元素的值
	Linklist p;
	int j;//计数器
	p = L->next;
	j = 1;
	while(p && j<i)
	{
		p = p->next;
		j++;
	}
	if( !p || j>i)
		return ERROR;
	e = p->data;
	return OK;
}

查找
这里的查找算法呢是最简单的一种,就是从最开始一路进行比较,返回查找结果。

LNode *LocateElem(Linklist L,ElemType e)
{//在带头结点的单链表L中查找值为e的元素
	p = L->next;
	while(p && p->data != e)
		p = p->next;
	return p;
}

插入
插入对于初学者来说是比较难的一种算法,因为单链表的抽象加上插入算法步骤的严格造成很多时候的出错。所以能够理解插入算法说明已经能够掌握单链表的基本操作,能够掌握单链表的基本操作也必定能够掌握插入算法。

status ListInsert(LinkList &L,int i,ElemType e)
{//在带头结点的单链表L中第i个位置插入值为e的新结点
	LinkList s,p;
	int j;//计数器
	 
	p = L;
	j = 0;
	while(p && (j<i-1) )
	{
		p = p->next;
		j++;
	}
	if(!p || j>i-1)
		return ERROR;
	s = new LNode;
	s->data = e;
	s->next = p->next;
	p->next = s;
	return OK;
}

删除
删除是插入的“逆运算”。

status ListDelete(Linklist &L,int i)
{//在带头结点的单链表L中,删除第i个元素
	LinkList p;
	int j;
	p = L->next;
	while((p->next) && (j<i-1) )
	{
		p = p->next;
		++j;
	}
	if( !(p->next) || ( j>i-1))
		return ERROR;
	q = p->next;
	p->next = q->next;
	delete q;
	return OK;
}

基础实例

这个部分构造一个数据结构比较简单的链表,实现它的各种操作。

要求:用链表记录一组整型数字,实现链表的基本操作。

分析:根据上面给出的各个操作的“伪代码”,这里设计了一个数据域是一个整型数据的单链表。(实际上用数组就好了,但是为了简化代码只能这样了。)

/***************************************************
 *Authors		:Philip
 *Email			:cf20090901@outlook.com
 *Last modified :2016-11-18 17:24
 *Filename		:LinkList_sample.cpp
 *Desciption	:在一个包含整型数据的链表中实现单链表的各种基本运算
 *				 
 *				 
 ***************************************************
*/

#include <iostream>
using namespace std;

typedef struct LNode{
	int number;//数据域
	struct LNode *next;//指针域
}LNode,*LinkList;

//*********显示所有***************
void ShowAll(LinkList &L){
	LinkList p;
	p = L->next;
	
	cout<<"所有信息如下:"<<endl;
	while(p){//遍历表并显示每个结点
		cout<<p->number<<"\t";
		p = p->next;
	}
	cout<<endl<<"总数为:"<<L->number<<endl;
}

//***********初始化***********
void InitList(LinkList &L){
	L = new LNode;
	L->next = NULL;
	L->number = 0;
	cout<<"单链表已经初始化!"<<endl;
}

//************创建链表***************
void CreatList(LinkList &L){
	LinkList r,p;
	int n;
	int i;//计数器
	int choice;
	
	cout<<"请输入个数->"<<endl;
	cin>>n;

	cout<<"请输入数据:"<<endl;
	r = L;
	for(i = 0; i<n ;i++){
		p = new LNode;
		cin>>p->number;
		p->next = NULL;
		r->next = p;
		r = p;	
	}
	L->number += n;

	cout<<"请问要显示所有结点吗?1/是,2/否"<<endl;
	cin>>choice;

	if(choice == 1){
		ShowAll(L);	
		return;
	}else return;
}


//************根据位置查找**********
void GetElem(LinkList &L){
	LinkList p;
	int i,j;//j是计数器
	p = L->next;
	j = 1;

	cout<<"请输入查找的位置->"<<endl;
	cin>>i;

	if(!p && j>i){
		cout<<"输入错误,请重新输入"<<endl;
		GetElem(L);
		return;
	}

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

	cout<<"查找结果是->"<<p->number<<endl;
	
	cout<<"是否继续?1/是 2/否"<<endl;
	cin>>i;
	if(i == 1){	
		GetElem(L);
		return;
	}
	else return;
}

//***********根据结点信息查找************
void Search(LinkList &L){
	LinkList p;
	int i,e,j;
	p = L->next;
	j = 1;
	cout<<"请输入查找的信息->"<<endl;
	cin>>e;

	while(p && p->number != e){	
		p = p->next;
		j++;
	}

	if(!p)
		cout<<"没有你要查找的信息"<<endl;

	else 
	{
		cout<<"查找结果是->"<<p->number<<endl;
		cout<<"位置是"<<j<<endl;
	}
	cout<<"是否继续?1/是 2/否"<<endl;
	cin>>i;
	if(i == 1)
	{	
		Search(L);
		return;
	}
	else return;

}

//********插入*********
void ListInsert(LinkList &L){
	LinkList s,e,p;
	int i,j;//计数器

	e = new LNode;
	p = L;
	j = 0;

	cout<<"请输入你要插入的位置->"<<endl;
	cin>>i;

	if(!p && j>i-1){
		cout<<"输入错误,请重新输入"<<endl;
		ListInsert(L);
		return;
	}

	cout<<"请输入要插入的信息->"<<endl;
	cin>>e->number;

	while(p && j<i-1 ){
		p = p->next;
		j++;
	}
	s = new LNode;
	s->number = e->number;
	s->next = p->next;
	p->next = s;
	L->number++;

	cout<<"请问要显示所有结点吗?1/是,2/否"<<endl;
	cin>>i;

	if(i == 1){
		ShowAll(L);	
		return;
	}else return;

}

//*************删除指定位置的结点*****
void ListDelete(LinkList &L){

	LinkList p,q;
	int i,j;

	cout<<"请输入要删除的位置->"<<endl;
	cin>>i;

	j = 1;
	p = L->next;

	if( !(p->next) || ( j>i-1)){
		cout<<"输入有误"<<endl;
		return;
	}

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

	q = p->next;
	p->next = q->next;
	delete q;
	L->number--;

	cout<<"请问要显示所有结点吗?1/是,2/否"<<endl;
	cin>>i;

	if(i == 1){
		ShowAll(L);	
		return;
	}else return;

}

void main(){
	int choice = 0;
	LinkList L;

	cout<<"*******************************"<<endl
		<<"*1.初始化"<<endl
		<<"*2.创建表"<<endl
		<<"*3.根据位置查找"<<endl
		<<"*4.根据信息查找"<<endl
		<<"*5.插入"<<endl
		<<"*6.删除"<<endl
		<<"*7.显示所有"<<endl
		<<"*8.退出"<<endl
		<<"*******************************"<<endl;

	while(choice != 8){
		cout<<endl<<"**********************************"<<endl<<"请选择->"<<endl;
		cin>>choice;

		switch(choice){
			case 1:	InitList(L);		break;
			case 2:	CreatList(L);		break;
			case 3:	GetElem(L);		break;
			case 4:	Search(L);		break;	
			case 5:	ListInsert(L);		break;	
			case 6:	ListDelete(L);		break;	
			case 7: ShowAll(L);		break;
			case 8: break;
			default:break;
		}
	}
	cout<<"程序运行结束!"<<endl;
}

这个程序呢比较简单,有一定的应对错误输入和重复相同操作的能力。我本人喜欢把主函数的结构做简单明了一点,所以大部分的输入输出操作和判断都放在了被调用的函数中。还有就是精简主函数比较重要的一个方面就是传递参数的个数要控制。

本来想要粘贴一个相对要复杂一些的程序,但是碍于篇幅就暂时不要了,但是所有操作在上面的实例中已经能够很好地体现了。

初学单链表的难点主要是在指针域的操作上,注意各种操作对指针域是如何使用的有利于尽快熟悉。当然,也正是因为指针操作是难点,所以对于初学C/C++的人来说容易对指针产生畏难情绪。

学习永无止境,编程更是如此,继续努力!

这是我写的第一篇博文,经验不足,水平有限,有错误或者可以优化的地方欢迎大家指证。(有人检索我的博文吗?:))一般情况下粘贴在网络上的代码大都是关键代码,但是这里借我老师的观点,初学者借鉴别人的代码无可厚非,粘贴完整代码是为了避免大家耗时去编译代码(这种经历相信大家都有过),所以希望各位能够尽快掌握这些知识。最后非常感谢各位检索到我的博文:)

  • 15
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值