精华内容
下载资源
问答
  • jdk1.7 HashMap中的”致命错误”:循环链表 jdk1.7 HashMap结构图 jdk1.7是数组+链表的结构 jdk1.7版本中主要存在两问题 头插法会造成循环链表的情况 链表过长,会导致查询效率下降 jdk1.8版本针对jdk1.8进行优化...
  • 循环链表C语言实现

    2018-02-03 17:48:33
    代码包含以下功能,经过产品验证,代码运行可靠。 1.创建链表 ...9.将游标重置指向链表中的第一数据元素; 10.将游标移动指向到链表中的下一数据元素; 11.直接指定删除链表中的某个数据元素。
  • 单向循环链表

    2021-01-20 03:32:26
    从我画的循环链表图中,你应该可以看出来,它像一环一样首尾相连,所以叫作“循环”链表。 操作 is_empty() 判断链表是否为空 length() 返回链表的长度 travel() 遍历 add(item) 在头部添加一节点 append(item)...
  • 循环链表c++

    2018-06-10 11:26:37
    使用c++语言实现的循环链表,功能齐全,包括插入、删除、寻找等功能
  • C++实现的带头结点的双向循环链表, 数据结构课设.。
  • 主要介绍了C语言数据结构之判断循环链表空与满的相关资料,希望通过本文能帮助到大家,让大家掌握这部分内容,需要的朋友可以参考下
  • 对于一个循环链表来说,其首节点和末节点被连接在一起。这种方式在单向和双向链表中皆可实现。要转换一个循环链表,可以选择开始于任意一节点然后沿着列表的任一方向直到返回开始的节点。再来看另一种方法,循环...
  • 遇到一问题就是双向循环链表的实现,改指向的时候总是发蒙。 我自己尝实现了一python的双向循环链表。附上代码,希望对大家有帮助。 如果不懂什么是双向循环链表的伙伴,需要补习一下数据结构的基础之后哦~~~ 在...
  • Java实现循环链表

    2020-03-22 23:21:24
    用Java定义一个循环链表,实现链表的基本操作: 初始化*、获取头结点、添加新元素*、删除链表元素 、获取链表元素*、查找链表元素*、更新链表中某个元素、 判断链表是否为空、求链表元素个数、输出链表元素、清空...
  • c语言实现单向循环链表
  • c++实现的循环链表

    2019-04-09 09:15:06
    使用c++实现的循环链表程序,供大家学习数据结构参考使用
  • 若是不清楚链表的结构,该篇文章不适合观看,这里只做文字说明,没有链表结构的图示


    前言:
    线性表是我们最常用的一种数据结构,线性表包含顺序表和链表,顺序表典型应用就是我们常用的ArrayList,链表的典型应用其中就有我们常用的LinkedList。LinkedList他的底层就是使用链表来存储数据元素的。这篇文章用以总结链表中的双向循环链表,为单链表的结点增加一个指向前驱的指针域,单链表就变成了双链表,将双链表的头尾相连,双链表就成了双向循环链表。

    一、相关概念

    第一部分主要介绍下和链表有关的相关概念,这里给出的概念都是书本上的官方定义,并不是作者胡诌的,为什么给出这些官方的定义呢 ?因为笔者认为官方给出的定义,是对一个概念最本质的解释,它的解释经过了时间的考验,被认为是解释的最合理深入简明扼要的。

    1.什么是线性表

    线性表是由n个数据元素构成的有限序列,n为线性表的表长,当n为0时表示当前线性表是空表。线性表有且仅有一个开始结点和终端节点。且开始结点没有前驱,终端节点没有后继,其余节点有且仅有一个前驱和后继。线性表一般分为顺序表和链表。

    2.什么是顺序表

    采用顺序存储结构存储数据元素的线性表被称为顺序表,顺序表,要求逻辑上相邻的数据元素在物理内存的存储上也是相邻的,当在顺序表的第i(0<=i<=n)插入一个数据元素时,需要后移n-i+1个数据元素,当删除第i个元素时需要移动n-i个数据元素。java实现顺序表

    3.什么是链表

    采用链式存储结构存储数据元素的线性表被称为链表,链表不要求逻辑上相邻的数据元素内存上必须相邻,链表的每个节点都包含两部分,一部分是数据域用以存储数据,一部分是指针域用以存储指向相邻结点的指针或者引用。链表通过每个节点的指针域将一串数据串联成链。当结点只有一个指针域时,被称为单链表。单链表-含头结点单链表-不含头结点

    4.单链表、双链表、循环单链表、循环双链表

    当链表的结点只有一个指针域时被称为单链表,循环单链表是单链表的一种特殊变化,只是将尾结点又指向了头结点(也可能是首结点)。
    当链表的结点有两个时,一个指向前驱,一个指向后继这种链表便是双链表,循环双链表是双链表的一种特殊变化,只是将尾结点又指向了头结点(也可能是首结点)。
    所有的链表的实现均有两种方式,一种是带有头结点,一种是不带有头结点的实现方式,两种实现略有区别。

    5.为什么需要循环链表?

    循环链表一般就是指循环单链表,不特殊指明是双向循环链表,那么该名称描述的就是单项循环链表,之所以需要循环链表是因为在操作系统中,循环链表有特定的应用场景,在一些场景中,链表中的元素需要循环的执行,但是在实际的开发中应用最多的还是双向循环链表。

    6.为什么需要双向链表?

    若是给定了一个结点,根据当前结点获取该结点的上一个结点,那么我们是没有办法直接获取的,只能是遍历链表,但若是使用了双向链表,我们则可以直接获取到该元素的上一个结点,时间复杂度就变成了O(1)。所以双向链表的实际应用意义更强,将双向链表的首尾相连就成了双向循环链表,双向循环链表的应用最常见的就是LinkedList。

    7.头结点和首结点

    首节点:真正存储第一个数据元素的结点被称为首节点。
    头结点:当链表采用带头结点方式实现时,会创建一个数据域为null的节点,该节点的指针域存储的指针指向首节点的指针,他的唯一作用是标识链表的开始,带有头结点的链表更易于操作。

    8.常见的栈和队列与线性表的关系

    栈和队列也是常见的数据结构,但是栈和队列并不是线性表的一种,线性表只包含了顺序表和链表。不过我们可以将栈和队列看成是特殊的线性表。怎么特殊呢,栈是被限制了只能在一端进行插入和删除操作的线性表,所以栈的特性是先入后出,队列是被限制了只能在一端插入另一端删除的线性表,所以队列的特性是先入先出。既然栈和队列都是特殊的线性表,那么栈和队列自然就可以使用线性表来实现了,通常可以使用线性表中的顺序表和队列来实现栈和队列。

    二、实现过程

    单链表-含头结点
    单链表-不含头结点
    上面是两种单链表的实现方式,其实无论是双链表还是双向循环链表他的实现方式都是类似的,区别都很有限,上面两篇文章里详细实现了单链表的各种常用方法,因此在该文章里只会总结必须用到的几个方法,比如插入、删除、清空、长度、判空、根据下标获取等,其他方法的实现都不难,就不一一展示了。

    1.提供节点类:DupNode

    双向循环链表的实现,我们首先必须为其提供一个结点类,该类需要有三个属性,一个数据域,一个指向前驱的指针域,还有一个指向后继的指针域,然后提供必要的构造方法即可,如下:

    /**
     * @author pcc
     * @version 1.0.0
     * @className DupNode
     * @date 2021-04-19 16:43
     * 该类是双向链表的结点类
     * 该类包含了数据域,后继指针域、前驱指针域三个属性。
     */
    public class DupNode {
        Object object;
        DupNode prior;//前驱指针域
        DupNode next;//后继指针域
    
        public DupNode(){
            this(null);
        }
        public DupNode(Object object){
            this(object,null,null);
        }
        public DupNode(Object object,DupNode prior,DupNode next){
            this.object = object;
            this.prior = prior;
            this.next = next;
        }
    }
    

    2.提供双向循环链表的实现类:DoubleLinkedTable

    采用带有头结点的方式实现双向循环链表,因此我们需要定义一个头结点。然后提供初始化它的构造器。值得注意的是,在初始化头结点时,我们必须将他的前驱和后继都声明为自己,这样才是一个空的双向循环链表。

    public class DoubleLinkedTable {
        //头结点
        DupNode head;
    
        public DoubleLinkedTable(){
            head = new DupNode();
            head.prior = head;
            head.next = head;
        }
    }
    

    3.提供长度(length)、打印(display)、清空(clear)等方法

    这些方法的实现原理都很简单,求链表长就是遍历链表计数即可,打印也是遍历链表,清空则是将头结点的前驱和后继都声明为自己,下面是三个方法的实现:

        //长度
        public int length(){
            DupNode node = head.next;
            int j = 0;
            while(!node.equals(head)){
                j++;
                node = node.next;
            }
            return j;
        }
    
        //打印
        public void display(){
            DupNode node = head.next;
            while(!node.equals(head)){
                System.out.println(node.object);
                node = node.next;
            }
        }
        //清空
        public void clear(){
            head.next = head;
            head.prior = head;
        }
    

    4.提供根据下标插入方法:insert(int i,Object object)
    学习双向循环链表建议还是先学习单链表,会了单链表双向循环链表就是窗户纸,一桶就破,因为他们的实现思路都是一样的,只是稍微的变化而已。单链表的遍历我们的退出条件是找到尾结点就退出(node.next == null),循环链表肯定没有尾结点了,退出循环的条件就成了碰到头结点再退出(node ==head),另外一点区别就是双向循环链表的赋值问题,我们需要为新结点的前驱指针和后继指针赋值,同时需要为新结点的上一个节点的后继后继指针从新赋值,还需要为新节点的后继结点的前驱指针重新赋值,代码实现如下:

       /**
         * 思路:
         * 1.寻找下标为i-1的数据元素,注意退出循环的条件应该是node!=head
         * 2.赋值即可,循环链表的核心就是空表也会有循环体系
         * 3,赋值时,i+1位置的元素应该是node.next 所以,应为node.next最后赋值
         * @param i
         * @param object
         * @throws Exception
         */
        public void insert(int i,Object object) throws Exception{
            if(i<0 || i>length())
                throw new Exception("下标不合法");
             DupNode node = head;
             int j = 0;
             while(!node.next.equals(head) && j<i){
                 j++;
                 node = node.next;
             }
    //         DupNode newNode = new DupNode(object);
    //         node.next.prior = newNode;
    //         newNode.prior = node;
    //         newNode.next = node.next;
    //         node.next = newNode;
    
            //写成以下这种和上面注释的部分,效果一样,无区别
             DupNode newNode = new DupNode(object,node,node.next);
             node.next.prior = newNode;
             node.next = newNode;
        }
    

    到了这里,我们就可以初步测试下,双向链表的插入是否有效了,下面创建一个测试类测试下,如下图所示,将几个元素插入到了双向循环链表中,然后输出结果正常,说明插入方法实现无误。有了这些头插法、尾插法直接根据下标即可轻松实现,这里不展示了。
    在这里插入图片描述

    5.提供根据下标删除的方法:remove(int i)

    实现思路其实和单链表的删除是没有区别的:寻找到下标为i-1的数据元素,然后将他的后继更改为i+1的数据元素,然后将下标为i+1的数据元素的前驱更改为,下标为i-1的数据元素即可,实现如下:

        //删除
        public void remove(int i) throws Exception{
            if(i<0 || i>length()-1)
                throw new Exception("下标不合法");
            DupNode node = head;
            int j = 0;
            while(!node.next.equals(head) && j<i){
                j++;
                node = node.next;
            }
            node.next = node.next.next;
            node.next.prior = node;
        }
    

    然后来测试下删除方法,就测试下删除下标为2的元素吧,理论上删除后,输出的应该是:张三、李四、赵柳,如下图可见,输出无误,可见删除方法实现时无误的。
    在这里插入图片描述

    6.提供根据下标获取方法(get(int i))、根据指定结点获取前一个结点方法(getPrior)、根据指定结点获取后一个结点信息方法(getNext)

    上面也说过,双向链表解决的问题就是在获取指定结点的上一个结点时是无需遍历链表的,这样大大节省了时间成本,这里就测试下该方法的实现。三个方法的实现如下所示:

        //根据下标获取
        public Object get(int i) throws Exception{
            return getNode(i).object;
        }
    
        //根据下标获取其前一个元素
        public Object getPrior(int i) throws Exception{
            return getNode(i).prior.object;
        }
    
        //根据下标获取其后一个元素
        public Object getNext(int i) throws Exception{
            return getNode(i).next.object;
        }
    
        public DupNode getNode(int i) throws Exception{
            if(i<0 || i>length())
                throw new Exception("下标不合法");
            DupNode node = head.next;
            int j =0;
            while(node.equals(head) && j<i){
                j++;
                node = node.next;
            }
            return node;
        }
    

    下面我们来测试下这三个方法是否正确,使用李四所在结点来进行测试,李四的下标应该是1,传入1分别运行三个方法,若是正确应该输出的是:李四、张三、王五,如下图可见结果正确。
    在这里插入图片描述

    三、总结

    1.链表的缺点

    线性表的两种实现顺序表、链表。相比于顺序表,链表的缺点就是查找元素比较慢,查找元素的时间复杂度是O(n),而顺序表的时间复杂度是O(1),在查找上顺序表要优于链表,链表查找慢,就是它的缺点了,但是双向循环链表在一定程度上减少了查找时的时间复杂度,但是依然是不及顺序表的查找效率的,所以具体的使用场景还是静态数据适合使用顺序表,动态数据适合使用链表。

    2.链表的优点

    顺序表在指定下标x位置插入元素,组需要后移n-x+1个元素,若是删除下标为x的数据元素,则需要向前移动n-x个数据元素,但是链表则不需要移动任何元素,链表需要做的只是找到对应的元素将指针域中的指针进行更新即可。链表的插入和删除的时间复杂度可以看成O(1),而顺序表插入和删除操作的都是O(n).所以链表的优点就是插入和删除比较快。

    3.如何使用链表

    所以综合链表的优点与缺点我们可以发现,顺序表更适合存储“静态”型数据,而链表更适合存储“动态”型数据,何为动态型呢,就是那些插入、删除操作比较频繁的数据。这类数据更适合使用链表存储。而数据结构不易改变的数据使用顺序表存储更合适。顺序表可类比java中的ArrayList,链表可类比java中的LinkedList。这两者都是顺序表与链表的典型应用。

    展开全文
  • 通过循环链表实现约瑟夫环问题,用c语言实现。属于数据结构部分内容
  • VC++双向循环链表

    2021-03-17 11:12:19
    摘要:VC/C++源码,其它分类,双向链表 VC++双向循环链表,实现功能:...查找和删除链表数据、清屏、清空链表等,如果你对VC++的双向循环链表不太熟悉,这例子对你的是比较有用的。 运行环境:Windows/Visual C/C++
  • 约瑟夫环问题,是一经典的循环链表问题,题意是:已知 n 个人(以编号1,2,3,…,n分别表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报到 m 的那个人出列;他的下一人又从 1 还是顺时针开始报...
  • 本文代码均在turbo C 2.0 的环境下运行通过,并得到正确结果,本程序为用循环链表实现约瑟夫环,即有m个人站成一圆环,从某人(队列第一)开始报,约定从某开始的第n个人出列,他的下一再从一开始报,然再...
  • 单链表、循环链表、双向循环链表总结

    千次阅读 多人点赞 2019-11-23 18:56:31
    链表介绍 不带头结点的单向链表 带头结点的单向链表 循环链表 双向循环链表

    链表介绍

    结点的概念:

    一个结点包含两个信息,一个是数据域和一个是指针域:

    • 数据域存储该结点的数据信息
    • 指针域存储其直接后继的位置,其示意图如下:
      在这里插入图片描述

    链表的概念:

    每个结点的存储单元是独立的,若干个结点通过指针域依次相链接可构成一个链表,这样的结构称为线性表的链式存储,简称链表。其示意图如下:
    在这里插入图片描述

    为了能够表示链表的开始和结束,需要增加头指针L表示链表的开始,而指针域设置为NULL表示结点的结束。

    不带头结点的单向链表

    在这里插入图片描述
    不带头结点的单向链表是最原始的链表,其头指针指向第一个数据元素,若是空链表其头指针的值为NULL。
    在这里插入图片描述
    由于不带头结点的链表在第一个元素插入和删除时带来不方便,因此链表初始化时,我们都需要增加一个头结点,并令头指针指向头结点,后续的链表默认都是带头结点的。其区别参见这篇文章 链表头结点和不带头结点的区别

    带头结点的单向链表

    在没有特殊指明情况下,我们创建的链表默认都应该带头结点,其初始化状态为如下图所示:
    在这里插入图片描述
    带有数据元素的链表示意图:
    在这里插入图片描述

    循环链表

    循环链表是单向链表的一种特殊形式,可以用于解决约瑟夫环问题。循环链表的最后一个结点的后继指向的是头结点,而不是NULL。其空循环链表的示意图如下:
    在这里插入图片描述
    带有数据元素的循环链表示意图如下:
    在这里插入图片描述

    在某些特殊情况下(比如需要频繁以追加方式插入新结点),我们可以令头指针指向最后一个结点,或者新增加一个尾指针一直指向最后一个结点,其示意图如下:
    在这里插入图片描述
    当头指针指向最后一个结点时或者增加尾指针,可以使得查找链表的开始结点和最后一个结点都方便,其执行时间为O(1). 而在一般情况下,其执行时间为O(n)。

    双向循环链表

    单向链表存在一个弊端就是,当需要获取某个结点p的前驱时,需要从头指针开始遍历链表,获得“前驱”的执行时间为O(n),为了克服单向链表的这种缺点,可以利用双向链表。

    在双向链表中有两个指针域,一个是指向前驱结点的prev,一个是指向后继结点的next指针。下图是空的双向循环链表示意图:

    在这里插入图片描述

    带有数据元素的双向循环链表示意图如下:

    在这里插入图片描述

    双向循环链表的插入和删除结点执行时间均为O(1),为常量级。和其他结构的链表体现了其优越性。

    总结

    以下这张是带头结点的单链表、循环链表以及双向循环链表的总结:

    在这里插入图片描述

    展开全文
  • 用C++实现的循环链表

    2020-04-15 13:23:29
    这是一循环链表,具备基本的操作,在普通链表的基础上,实现了定长循环链表的循环输入,判断链表是否有环等较为特殊的操作。增删改查自然也有。
  • 05.循环链表仿真链表以及循环链表应用
  • 主要介绍了C语言中双向链表和双向循环链表详解的相关资料,需要的朋友可以参考下
  • 图展示的是一单向循环链表,他跟以上的单向链表对比只是多了一指向头结点的 指针,因此,他们的算法几乎是一样的。 第一,设计节点。 单向循环链表的节点跟单向链表完全一致。 第二,初始化空链表。 跟单向链表...

    每日一句

    It’s gonna be ok.

    一切都会好的。

    内容概要

    单向循环链表

    首先来看看图示:
    单链表

    图展示的是一个单向循环链表,他跟以上的单向链表对比只是多了一个指向头结点的 指针,因此,他们的算法几乎是一样的。
    第一,设计节点。 单向循环链表的节点跟单向链表完全一致。

    第二,初始化空链表。 跟单向链表类似,我们既可以初始化一个带有头结点的空循环链表,也可以不要头结点:
    头节点

    这边把两个步骤放在一起了

    linklist init_list(void) // 带有头结点的单循环链表 
    { 
    	linklist head = malloc(sizeof(listnode)); 
    	head->next = head;
    	return head; 
    }
    

    然后就是插入节点的操作,跟单链表一样,可以去看我前面的文章,链接https://blog.csdn.net/qq_41835250/article/details/107568684

    第三,由于单循环链表的特点,删除一个指定的节点时我们可以不需要头指针,而仅仅通过该 待删除节点的指针就可以回溯到他的前一个节点,进而实现删除操作:

    void remove_node(linklist delete) 
    { 
    	linklist tmp = delete; 
    	while(tmp->next != delete) // 从 delete开始,绕一圈找到其前面的节点
    	{ 
    		tmp = tmp->next;
    	}
    	tmp->next = delete->next; 
    	delete->next = NULL;
    }
    

    第四,移动节点。 在单链表中,移动节点需要三个参数,因为移动的目的地可能在原始位置的前面,由于 没有头指针的情况下无法回溯回去,因此必须要链表的头指针来遍历。但是对于单循环链表
    而言则不存在这个问题,链表中的任意一个节点,都可以从另一个任意节点出发找到,因此 移动节点不需要三个参数,请看代码:

    void move_node(linklist p, linklist anchor) 
    { 
    	if(p == NULL || anchor == NULL) 
    	return;
    	remove_node(p); // 先将要移动的节点从链表中剔除 
    	insert_node(anchor, p); // 再插入指定的地方
    }
    

    第五,查找节点。 同理,单循环链表的查找跟单链表的查找基本是一样的,只是在判断是否遍历完链表的 条件上有所差别:

    linklist find_node(linklist mylist, int data) 
    { 
    	if(is_empty(mylist)) 
    		return NULL;
    	linklist tmp;
    	for(tmp=mylist->next; tmp!=mylist; tmp=tmp->next) 
    	{
    		if(data == tmp->data) 
    			break; 
    	} 
    	return tmp==mylist ? NULL : tmp; // 如果遍历回到 mylist 则表示找不到
    }
    

    双向循环链表

    通过上面的内容,可以看出来单循环链表的缺点就是每一次遍历都需要从头开始,不能从任意位置来遍历,于是双向循环链表的出现就是来解决这个问题的。
    第一,设计节点。 双向链表的节点当然需要两个指针,节点的设计如下:
    双链表

    typedef struct node
    {
    	int data;
    	struct node *prev;
    	struct node *next;
    }listnode, *linklist;
    

    这里需要注意一下,:节点中的 prev 和 next 指针均是 struct node 型的指针,他们都是指向本结 构体类型的相邻节点的。
    第二,初始化空链表。
    因为一般情况下我们所使用的大都是带头结点的双向循环链表,所以我就直接使用带头结点的。

    linklist init_list(void)
    {
    	linklist mylist = malloc(sizeof(listnode));
    	if(mylist != NULL)
    	{
    		mylist->prev = mylist->next = mylist;
    	}
    	return mylist;
    }
    

    第三,插入节点
    由于双向循环链表的特征,所以可以有两种插入方式,头插法和尾插法,先来看看头插法的图示步骤:
    头插法
    第 1 步,将新节点的 prev 指针指向 anchor 节点:new->prev = anchor
    第 2 步,将新将节点的 next指向 anchor 下一个节点:new->next = anchor->next
    第 3 步,将节点 anchor 的 next 指针指向新节点 new:anchor->next = new
    第 4 步,将 anchor 原来的下一个节点的 prev 指针指向 new 节点: new->next->prev = new
    把它们链接起来就是

    void insert_next(linklist new, linklist anchor) 
    { 
    	if(new == NULL || anchor == NULL) 
    		return;
    	new->prev = anchor; // 第①步 
    	new->next = anchor->next; // 第②步
    	
    	anchor->next = new; // 第③步 
    	new->next->prev = new; // 第④步
    }
    

    尾插法也类似,不过是把节点插在头节点的后面
    还是画个图示:
    尾插法
    步骤:
    第 1 步,将新节点的 prev 指向 anchor 的前节点:new->prev = anchor->prev
    第 2 步,将新节点的 next 指向 anchor:new->next = anchor
    第 3 步,将 anchor 的前节点的 next 指向新节点:anchor->prev->next = new
    第 4 步,将 anchor 的前驱指针指向 new:anchor->prev = new;
    代码:

    void insert_prev(linklist new, linklist anchor)
    { 
    	if(new == NULL || anchor == NULL) 
    		return;
    	new->prev = anchor->prev; 
    	new->next = anchor;
    	new->prev->next = new;
    	anchor->prev = new;
    }
    

    第四,删除节点。
    删除一个节点的步骤很简单,只需要将其前趋节点指向其后续节点,将其后续节点指向其前驱节点即可,另外要注意,需将被删除节点的前后向指针置空,使其彻底从链表中脱离 开来,防止错误的访问。

    void remove_node(linklist delete) 
    { 
    	if(delete == NULL) 
    		return;
    	delete->prev->next = delete->next;  delete->next->prev = delete->prev; 
    	delete->prev = NULL;  
    	delete->next = NULL; //这两行代码用来将要删除的节点彻底脱离
    }
    

    第五,移动节点。
    移动节点很好理解,就是一个删除一个移动,画个图就很好理解
    移动节点
    代码

    void move_next(linklist p, linklist anchor) 
    { 
    	remove_node(p); // 将要移动的节点从链表中移除
    	insert_next(p, anchor); // 将节点插入锚点 anchor 的后面 
    }
    

    第六,查找节点。 节点的查找无非就是对链表进行遍历,从头开始找,找一圈找到为止。直接上代码:

    linklist find_node(int data, linklist mylist)
    {
    	if(is_empty(mylist))
    		return NULL;
    
    	linklist tmp = mylist->next;  // 让 tmp从第一个节点开始
    
    
    	while(tmp != mylist)
    	{
    		if(tmp->data == data)
    			return tmp;
    
    		tmp=tmp->next; // 不断地向后遍历,查找
    
    	}
    	return NULL;
    }
    

    如果看到这里你都已经基本看明白了,那么你用双向循环链表写一个学生管理系统什么的已经没问题了,以前我也是学到这里也做过,如果需要欢迎私信。
    写到这里突然想起来Linux里还有一个叫做内核链表的东西,下一篇更新一下内核链表。
    每一个功能我都写成函数以便于调用,如有问题,欢迎指正。
    感谢观看。

    展开全文
  • 双向循环链表

    2018-05-10 09:57:38
    摘要:VC/C++源码,其它分类,双向链表 VC++双向循环链表,实现功能:创建...查找和删除链表数据、清屏、清空链表等,如果你对VC++的双向循环链表不太熟悉,这例子对你的是比较有用的。 运行环境:Windows/Visual C/C++
  • 数据结构-基本算法-不带头结点的循环链表(学生时代源码,调试可运行)
  • 循环链表实现约瑟夫环问题 约瑟夫环是一数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报到m的那个人出列;他的下一人又从1开始报到m的那个人又...
  • 单链表的一变形是单向循环链表,链表中最后一节点的next域不再为None,而是指向链表的头节点。 操作 is_empty() 判断链表是否为空 length() 返回链表的长度 travel() 遍历 add(item) 在头部添加一节点 ...
  • 循环链表实现队列操作 讲解详细 通过多次编译 可以运行的
  • C语言版双向循环链表,双向循环链表经典程序,用于指针进行编写的C语言程序。。。
  • 循环链表的逆置

    2013-05-02 14:58:09
    循环链表的逆置的完整介绍,能让你瞬间理解,希望能对你有帮助
  • 循环链表就是最后一结点的指针域指向头结点,整个链表形成一环。 创建一结点类: class Node(object): """创建一结点类""" def __init__(self, data): self.data = data self.next = None 创建一...

    循环链表

    循环链表就是最后一个结点的指针域指向头结点,整个链表形成一个环。

    创建一个结点类:

    class Node(object):
        """创建一个结点类"""
        def __init__(self, data):
            self.data = data
            self.next = None

    创建一个创建循环链表的类:

    接下来的一些操作循环链表的函数,都将写在这个类中

    class create_circular_linked_list(object):
        """创建一个创建循环链表的类"""
        def __init__(self):
            self.head = None
    
        def is_empty(self):
            """判断循环链表是否为空"""
            return self.head is None

    获取循环链表的长度:

    大体流程:

    1. 先在while中判断当前结点是否为空,若当前结点不为空,则长度加一
    2. 然后再判断是否为尾结点,如果是不是尾结点,继续后移判断
    def length(self):
        """获取循环链表的长度"""
        cur = self.head
        count = 0
        while cur is not None:
            count += 1
            # 如果当前结点的下一个结点是头结点,说明这个结点就是尾结点
            # 如果不是,就将指针向后移动一个
            if cur.next == self.head:
                break
            else:
                cur = cur.next
        return count

    遍历链表,并打印出来:

    为什么要考虑链表为空的情况?

    答:如果直接就需要使用cur.next,这种情况就需要考虑链表为空的情况。

    因为如果链表为空时,是没有.next属性的,.next属性是结点的属性,空链表没有结点,所以需要考虑链表为空的情况。

    def travel(self):
        """遍历链表"""
        if self.is_empty():
            return
        cur = self.head
        print(cur.data)
        while cur.next != self.head:
            cur = cur.next
            print(cur.data)

    在头部添加结点(头插法):

    如果链表为空的情况:添加的结点就是头结点,头结点的后继结点还是头结点

     

    如果链表不为空的情况: 

    1. 将指针移动到尾部结点
    2. 尾部结点指向新节点
    3. 新结点指向原来的头结点
    4. 再将头结点的称号给新结点

    def add_first(self, data):
        """在头部添加结点"""
        node = Node(data)
        if self.is_empty():
            self.head = node
            node.next = self.head
        else:
            cur = self.head
            # 将指针移动到尾部结点
            while cur.next is not self.head:
                cur = cur.next
            # 尾部结点指向新节点
            cur.next = node
            # 新结点指向原来的头结点
            node.next = self.head
            # 再将头结点的称号给新结点
            self.head = node  

    在尾部添加结点(尾插法):

    如果链表为空的情况:添加的结点就是头结点,头结点的后继结点还是头结点

    如果链表不为空的情况:

    1. 先将指针移动到尾部
    2. 尾部结点指向新结点
    3. 新结点指向头结点

    (不知道你有没有发现,循环链表的头插法和尾插法很相似,只差最后一行代码,可以思考一下这是为什么)

    def add_last(self, data):
        """在尾部添加结点"""
        node = Node(data)
        if self.is_empty():
            self.head = node
            node.next = self.head
        else:
            cur = self.head
            # 将指针移动到尾部
            while cur.next is not self.head:
                cur = cur.next
            # 尾部结点指向新结点
            cur.next = node
            # 新结点指向头结点
            node.next = self.head

    在指定位置插入结点:

    三种情况:

    1、插入出错:索引位置不在链表长度范围之内

    2、插入头部:直接调用头插法的函数

    3、插入中间:

    1. 指针移动到插入位置
    2. 将插入位置的前一个结点(pre)指向新结点
    3. 新结点指向原来的当前结点(cur)

    (当链表为空时,我们只能插入到第0个位置,也就是调用头插法即可,当链表不为空时,即可正常插入)

    def insert_node(self, index, data):
        """在指定位置插入结点"""
        node = Node(data)
        if index < 0 or index > self.length():
            print("插入位置错误")
            return False
        elif index == 0:
            self.add_first(data)
        elif index == 0:
            self.add_last()
        else:
            cur = self.head
            pre = None # pre为当前指针所指向结点的前一个结点
            count = 0
            # 将指针移动到要插入的位置
            while count < index:
                pre = cur
                cur = cur.next
                count += 1
            pre.next = node
            node.next = cur

    删除指定结点:

    1、链表为空的情况:没有结点可删除,直接返回

    2、链表不为空的情况:

    1、要删除的结点就是头结点:

            1、指针移到尾部

            2、尾结点指向头结点的下一个(跳过头结点)

            3、将头结点的称号给头结点的下一个(让原头结点的下一个,成为新的头结点)

    2、要删除的结点不是头结点

            1、移动到要删除结点的位置

            2、将要删除结点(cur)的前驱节点(pre)指向要删除结点(cur)的后继结点(如下图所示,也就是跳过了中间要删除的结点,以达到删除的目的)

    def remove_node(self, data):
        """删除指定结点"""
        if self.is_empty():
            return
        # 如果要删除的结点就是头结点
        elif data == self.head.data:
            # 如果链表只有一个头结点
            if self.head.next is self.head:
                self.head = None
            else:
                cur = self.head
                while cur.next != self.head:
                    cur = cur.next
                cur.next = self.head.next
                self.head = self.head.next
        else:
            cur = self.head
            pre = None
            # 移动到要删除结点的位置
            while cur.data != data:
                # 如果没找到
                if cur.next == self.head:
                    return
                pre = cur
                cur = cur.next
            # 将要删除的结点的前驱结点指向后继结点,这样就跳过了中间的结点
            pre.next = cur.next

     

    查找指定结点是否存在:

    (因为我们在while中直接判断当前结点是否为空,所以不需要再单独考虑链表是否为空的情况了)

    然后我们需要考虑三种情况:

    1. 找到所查结点:直接返回True,查找成功
    2. 已经查到尾部了(因为是循环链表,尾部的下一个又是头结点,会无限循环下去,所以如果查到尾结点还没有找到,那就可以直接返回False了)
    3. 没找到的情况:那就令指针后移,继续找下个结点
    def is_exist(self, data):
        """查找指定结点是否存在"""
        cur = self.head
        while cur is not None:
            # 找到所查结点
            if cur.data == data:
                return True
            # 已经查到尾部了
            elif cur.next == self.head:
                return False
            else:
                cur = cur.next
        return False

    主函数测试:

    if __name__ == '__main__':
        lists = create_circular_linked_list()
        lists.add_last(2)
        lists.add_first(1)
        lists.add_first(0)
        lists.add_last(3)
        lists.insert_node(2, 8)
        lists.travel()
        print("链表长度:", lists.length())
        lists.remove_node(8)
        lists.travel()
        print(lists.is_exist(2))

     测试结果截图:

    完整代码

    class Node(object):
        """创建一个结点类"""
        def __init__(self, data):
            self.data = data
            self.next = None
    
    class create_circular_linked_list(object):
        """创建一个创建循环链表的类"""
    
        def __init__(self):
            self.head = None
    
        def is_empty(self):
            """判断循环链表是否为空"""
            return self.head is None
    
        def length(self):
            """获取循环链表的长度"""
            cur = self.head
            count = 0
            while cur is not None:
                count += 1
                # 如果当前结点的下一个结点是头结点,说明这个结点就是尾结点
                # 如果不是,就将指针向后移动一个
                if cur.next == self.head:
                    break
                else:
                    cur = cur.next
            return count
    
        def add_first(self, data):
            """在头部添加结点"""
            node = Node(data)
            if self.is_empty():
                self.head = node
                node.next = self.head
            else:
                cur = self.head
                # 将指针移动到尾部结点
                while cur.next is not self.head:
                    cur = cur.next
                # 尾部结点指向新节点
                cur.next = node
                # 新结点指向原来的头结点
                node.next = self.head
                # 再将头结点的称号给新结点
                self.head = node
    
        def add_last(self, data):
            """在尾部添加结点"""
            node = Node(data)
            if self.is_empty():
                self.head = node
                node.next = self.head
            else:
                cur = self.head
                # 将指针移动到尾部
                while cur.next is not self.head:
                    cur = cur.next
                # 尾部结点指向新结点
                cur.next = node
                # 新结点指向头结点
                node.next = self.head
    
        def insert_node(self, index, data):
            """在指定位置插入结点"""
            node = Node(data)
            if index < 0 or index > self.length():
                print("插入位置错误")
                return False
            elif index == 0:
                self.add_first(data)
            else:
                cur = self.head
                pre = None # pre为当前指针所指向结点的前一个结点
                count = 0
                # 将指针移动到要插入的位置
                while count < index:
                    pre = cur
                    cur = cur.next
                    count += 1
                pre.next = node
                node.next = cur
    
        def remove_node(self, data):
            """删除指定结点"""
            if self.is_empty():
                return
            # 如果要删除的结点就是头结点
            elif data == self.head.data:
                # 如果链表只有一个头结点
                if self.head.next is self.head:
                    self.head = None
                else:
                    cur = self.head
                    while cur.next != self.head:
                        cur = cur.next
                    cur.next = self.head.next
                    self.head = self.head.next
            else:
                cur = self.head
                pre = None
                # 移动到要删除结点的位置
                while cur.data != data:
                    # 如果没找到
                    if cur.next == self.head:
                        return
                    pre = cur
                    cur = cur.next
                # 将要删除的结点的前驱结点指向后继结点,这样就跳过了中间的结点
                pre.next = cur.next
    
        def travel(self):
            """遍历链表"""
            if self.is_empty():
                return
            cur = self.head
            print(cur.data)
            while cur.next != self.head:
                cur = cur.next
                print(cur.data)
    
        def is_exist(self, data):
            """查找指定结点是否存在"""
            cur = self.head
            while cur is not None:
                # 找到所查结点
                if cur.data == data:
                    return True
                # 已经查到尾部了
                elif cur.next == self.head:
                    return False
                else:
                    cur = cur.next
            return False
    
    
    if __name__ == '__main__':
        lists = create_circular_linked_list()
        lists.add_last(2)
        lists.remove_node(2)
        print(lists.is_empty())
        lists.add_first(1)
        lists.add_first(0)
        lists.add_last(3)
        lists.insert_node(2, 8)
        lists.travel()
        print("链表长度:", lists.length())
        lists.remove_node(8)
        lists.travel()
        print(lists.is_exist(2))
    

    因为循环链表是一个闭合的环,所以我们遍历的时候,从链表的任意一个位置开始遍历都可以,而要注意的是设定好遍历结束的条件。  

    展开全文
  • 主要介绍了C++的循环链表与双向链表设计的API实现,文中的示例对于链表结点的操作起到了很好的说明作用,需要的朋友可以参考下

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 301,676
精华内容 120,670
关键字:

循环链表数个数