• 数据结构(一)—链表


    一、背景

    作为机械狗转行,数据结构当然是不可缺少的,疫情假期里闲来在家无事,刚好接下给小孩教数据结构的活,所以自学了简单的数据结构用法,算是数据结构的入门吧。做个笔记记下来,其实平常喜欢用思维导图进行记录,这算复习一遍,所以再用MarkDown进行记录,顺便发个博。

    话不多说,今天先来记录一下链表简单的概念、实现等。

    二、链表概念及特点

    概念

    链表是物理存储单元上非连续的、非顺序的存储结构,它是由一个个结点,通过指针来联系起来的,其中每个结点包括数据指针

    适用场合

    • 大内存空间分配

    • 元素频繁删除和插入

    • 若数据以查找为主,很少涉及增删,则选择数组

    基本操作

    书上主要都是利用抽象数据类型进行定义的,我这里直接用大白话,最终目的还是要理解每种数据结构的特点及实现。

    • 初始化
    • 插入
    • 删除
    • 查询
    • 取值

    分类

    1. 单链表

      单链表不用多做介绍,看一眼图都懂。

      image-20200315163441065

    2. 循环链表

      循环链表(Circular Linked List),表中最后一个结点的指针域指向头结点,整个链表形成一个环。

      image-20200315163618673

    3. 双向链表

      双向链表(Double Linked List),为了克服单链表查找某结点的直接前驱结点,必须要从表头指针出发的缺点。故在双向链表的结点中有两个指针域,一个指向后继,一个指向前驱,如下图。

      image-20200315163718451

    三、单链表的实现

    本文只先实现单链表,利用C++编码。链表这里的代码加了一些自己的方法和书本的不完全一样。直接上码。注意主函数的调用在最后哦。

    • 单链表的存储结构初始化链表

      首先是定义单链表的存储结构及初始化链表:

    #include<iostream>
    using namespace std;
    
    //单链表的存储结构
    typedef char ElemType; //存储元素类型
    typedef struct LNode
    {
    	ElemType data;//结点数据域
    	struct LNode *next;//结点指针域,指向下一结点
    }LNode,*LinkList;//LinkList为指向结构体LNode的指针类型
    
    //链表初始化
    void InitList(LinkList &L)
    {
    	L=new LNode;//生成新结点作为头结点,用头指针L指向头结点
    	L->next=NULL;//头结点指针域置空
    } 
    

    ​ 注意我们这里的头结点是一直不用的结点,数据域不存数据,第一个数据存到的是首元结点,不要混淆,如下图:

    image-20200315165758438

    ​ OK,这三个概念不要搞混淆,继续下一步。

    • 创建单链表

      创建单链表,创建单链表又可以分为前插法后插法,顾名思义,就是在链表头结点后插入新结点,还是链表尾部插入新结点。

    //创建单链表
    //前插法
    void CreateList_H(LinkList &L,int n)
    {//这里输入一个n,限定了链表的长度,可以不这么做,利用其它方式停止链表插入,
     //这里为了方便展示头插法的算法逻辑,因此不必在意这点
    	InitList(L);//先建立一个带头结点的空链表
    	for(int i;i<n;i++)
    	{
    		LNode *p=new LNode;//生成新结点*p
    		cin>>p->data;//输入数据
    		p->next=L->next;//令新结点指针域指向原首元结点
    		L->next=p;//令头结点指向新结点
    	} 
    }
    
    //尾插法
    void CreateList_R(LinkList &L,int n)
    {
    	InitList(L);//先建立一个带头结点的空链表
    	LNode *r=L;//临时指针r指向头结点
    	for(int i=0;i<n;i++)
    	{
    		LNode *p=new LNode;//生成新结点
    		cin>>p->data;//输入数据,填充数据域
    		p->next=NULL;//新结点指针域初始化为NULL
    		r->next=p;//将新结点*p插入尾结点 r->next后
    		r=p; //令r指向新的尾结点p
    	} 
    }
    
    • 插入

      下面我们来实现链表在任意位置的插入,先拿一个图来表示该插入过程,以便理解:

    image-20200315171409531

    ​ ①查找第ai-1个结点,并将指针p指向该结点;

    ​ ②生成新结点*s;

    ​ ③将新结点*s的数据域置为e;

    ​ ④将新结点*s的指针域指向ai;

    ​ ⑤将结点p的指针域指向新结点s。

    //单链表插入
    //在带头结点的单链表L中第i个位置插入值为e的新结点
    void ListInsert(LinkList &L,int i,ElemType e)
    {
    	LNode *p=L;
    	int j=0;
    	while(p!=NULL&&(j<i-1))
    	{//查找第i-1个结点,令p指向该结点
    		p=p->next;
    		j++;
    	}	
    	if(p==NULL||j>i-1)
    	{//这里判断i值是否存在或合法,当i>n+1或者i<1时,i值非法
    		cout<<"i值非法!"<<endl;
    		return;
    	}
    	
    	LNode *s=new LNode;//生成新结点*s
    	s->data=e;//新结点数据域填充
    	s->next=p->next;//将新结点的指针域指向p->next
    	p->next=s;//p->next指向s
    } 
    
    • 删除

      删除元素,老样子,先来个图。

      image-20200315173115999

      代码如下:

    //删除
    //在单链表L中,删除第i个元素
    void ListDelete(LinkList &L,int i)
    {
    	LNode *p=L;
    	int j=0;
    	while((p->next!=NULL)&&(j<i-1))
    	{//查找第i-1个结点,p指向该结点
    		p=p->next;
    		j++;
    	}
    	if(p->next==NULL||(j>i-1))
    	{//判断i值是否合理,当i>n或i<1时,删除位置不合理
    		cout<<"i值非法,删除失败!"<<endl;
    		return;
    	}
    	LNode *q=p->next;//临时保存被删结点,以备释放
    	p->next=q->next;//改变删除结点前驱结点的指针域
    	delete q; //释放删除结点的空间
    } 
    
    • 取值查找

      取值和查找的就简单了,来一起整。

    //取值//在单链表L中根据序号i获取元素的值
    ElemType GetElem(LinkList &L,int i)
    {
    	int j=1;
    	LNode *p=L->next;//初始化,p指向链表L的头结点,计数器j初值赋为1
    	while((p!=NULL)&&j<i)
    	{//顺着链表向后扫描,直到p为空或p指向第i个元素为止
    		p=p->next;//p指向下一个结点
    		j++;//计数器j++
    	} 
    	if(p==NULL||j>i)
    	{//判断i值是否合法,i>n或i<=0时,不合法
    		cout<<"i值不合法!"<<endl;
    		return NULL;
    	}
    	return p->data;//返回元素
    } 
    
    //查找
    //在单链表L中查找值为e的结点
    LNode* LocateElem(LinkList &L,ElemType e)
    {
    	LNode *p=L->next;//初始化,令p指向头结点
    	while(p!=NULL&&p->data!=e)
    	{//向后扫描,直到p为空或p指向结点的数据域为e
    		p=p->next;
    	} 
    	return p;//查找成功返回值为e的结点地址p,查找失败则p为NULL
    } 
    
    • 调用及链表打印

      这里我们进行调用以上方法,进行测试,同时打印穿件的链表。

    //打印链表
    void PrintList(LinkList &L)
    {
    	LNode *p=L->next;
    	while(p!=NULL)
    	{
    		cout<<p->data<<"	";
    		p=p->next;
    	}
    	cout<<"
    ";
    }
    
    int main()
    {
    	LinkList L1;
    	int n;
    	cout<<"请输入链表元素数量"<<endl; 
    	cin>>n;
    	CreateList_R(L1,n);//后插法创建链表
    	PrintList(L1);//打印链表
    	ListDelete(L1,2);//链表删除第2个元素
    	PrintList(L1);//打印链表
    	ListInsert(L1,3,'z');
    	PrintList(L1);
    	return 0;
    }
    

    以上代码测试结果如下:

    image-20200315174853167

    OK,以上查找和查询功能大家可以自己去测试,应该都是没问题的。

    欢迎大家留言讨论,转载请注明原文地址就好哟yo~

    参考引用:

    《数据结构(C语言版 第2版)》,好书啊,推荐入门小白去看~例如我,哈哈哈。

    写文不易~因此做以下申明:

    1.博客中标注原创的文章,版权归原作者 煦阳(本博博主) 所有;

    2.未经原作者允许不得转载本文内容,否则将视为侵权;

    3.转载或者引用本文内容请注明来源及原作者;

    4.对于不遵守此声明或者其他违法使用本文内容者,本人依法保留追究权等。

  • 相关阅读:
    [哈希][倍增] Jzoj P5856 01串
    [exgcd] Jzoj P5855 吃蛋糕
    [折半搜索][分治][二分] Jzoj P5851 f
    [lca][主席树] Jzoj P5850 e
    [二分][树状数组] Jzoj P5849 d
    [容斥] Jzoj P5843 b
    [前缀和][枚举] Jzoj P5842 a
    [平衡规划][模拟][前缀和] Jzoj P4724 斐波那契
    [spfa] Jzoj P4722 跳楼机
    [模拟] Jzoj P2499 东风谷早苗
  • 原文地址:https://www.cnblogs.com/gentlesunshine/p/12498969.html
Copyright © 2020-2023  润新知