c#数据结构-单链表

目录

一、为什么要学单链表

二、实现单链表

1.单链表的原理

1)网上的讲法

2)自己的理解

2.单链表的代码实现(增删改查等功能)

对单链表中每个方法的解释:

1)单链表末尾添加数据

2)往单链表里面插入数据

3)删除链表里某个元素并返回删除的元素

4)返回单链表的长度

5)清空单链表

6)判断单链表是否为空

7)传入位置,返回数据

8)传入数据,返回位置

三、双向链表和循环链表(简单提一下)

1.双向链表

2.循环链表


一、为什么要学单链表

假如我们需要按顺序存储56789这几个数字,先看一下顺序表和单链表是怎样存储的。

 由此可以看出顺序表在逻辑上相邻的元素,在物理上也相邻(数组就是顺序表)。单链表在逻辑上相邻的元素,在物理上不相邻,所以单链表有一个好处就是可以让内存充分的被使用(空间利用率高)所以顺序表在修改和查找某个元素时比单链表效率高,而顺序表在插入和删除元素时比单链表运行效率低(这两句话不懂的学完单链表的增删改查就懂了)。

先解释一下上面的红字(你得学完单链表才能看懂):假如你现在写了个顺序表List和一个单链表LinkList。你现在要给这俩表写修改和查找某个元素的方法,你会咋写,顺序表是不是直接通过下标直接就能找到需要修改的元素,比如你List用数组去实现,直接在修改和查找方法里写个arr[n],是不是就把想找的元素找到了而你单链表是不是得通过第一个元素一个一个挨着往后面遍历才能找到想要的元素,如果让你找第1000个元素,顺序表一下就能找到,而单链表就需要从第一个元素开始遍历,直到找到第1000个元素才能停下来。那为啥插入和删除元素运行效率正好相反,因为顺序表你想插入一个元素,需要把后面的元素整体向后移动,而单链表插入的话,直接把next的指向变一变就完事儿了。举个例子,假如这俩表有1000个元素,我需要往第5个元素后面插入一个元素,顺序表是不是得把5后面的元素全给遍历一遍,然后才能全往后整体移动;而单链表直接遍历到第5个元素,直接把next指向的位置换一换就行。删除也一样。

解释一下上面的黑体字(可以让内存充分的被使用):假如现在地址就剩这么多地方了,其他地方被1和2占了,那么这顺序表和单链表哪个才能继续存储56789,毋庸置疑肯定是单链表,因为顺序表的56789必须挨着存,挨着存不就超了么,存不下;而单链表的数据想存哪就存哪,不用非得挨着存。

总结一下:单链表是适合插入和删除操作较多的数据。也可以更好的利用内存(空间利用率高)。

二、实现单链表

1.单链表的原理

上面说单链表里的元素物理位置可以不相邻,那这是怎么做到的?下面来说一下。

先讲一下网上的说法,然后再说一下我理解的,我要自己理解的不对,欢迎大家给我指出问题。

1)网上的讲法

首先,你得先知道节点是什么。节点就是一个类Node,类里面存放了两个数据,一个是你想存储的元素date,一个是下一个Node实例的地址。节点是用来连接整个链表的。

单链表的其中一种表现形式:

就是通过第一个节点,我就能找到第二个节点,通过第二个节点,我就能找到第三个节点,以此类推。节点里面有数据,找到节点,不就能访问到里面的数据。

有的单链表里可以有头节点,因情况而论,头节点就是date为默认值,next里存放着第一个元素所在的那个实例的地址。

这是用另一种形式表达单链表,这个里面带着头节点了:

 上面这个图好好理解和记忆一下,对以后代码理解很有帮助。

实例是什么意思:建议看看刘铁猛老师的C#课。假如现在有个类Node,那么你声明一下Node a = new Node();这个a就是Node的一个实例。

2)自己的理解

我自己一直有个疑惑,比如这张图一个地址里能放两个不同类型的数据吗?放一个date还得再放一个next。我感觉可能是这样讲更好理解?所以我感觉,真正的底层存储应该是一个实例的date和next在两个不同的地址中存储,这个实例中的next指向下一个实例。有懂的人可以在评论里讲一讲。

2.单链表的代码实现(增删改查等功能)

 来个最简单的例子吧。假如我现在就需要存储整形数字,下面我们来实现单链表。

整个代码最下面有对每个方法的解释。

using System;

namespace _4.单链表
{
    class Program
    {
        static void Main(string[] args)
        {

        }
    }

    //首先咱们需要写一个节点
    class Node
    {
        private int data;
        private Node next;//这里你就理解为下一个实例的地址
        public Node(int data, Node next)
        {
            this.data = data;
            this.next = next;
        }
        public Node()
        {
            data = default(int);//int的默认值
            next = null; 
        }
        public Node(int data)
        {
            this.data = data;
            next = null;
        }
        public Node(Node next)
        {
            data = default(int);
            this.next = null;
        }

        public int Data
        {
            get { return data; }
            set { data = value; }
        }
        public Node Next
        {
            get { return next; }
            set { next = value; }
        }
    }
    class LinkList
    {
        private Node head;//头节点,里面data为默认值
        public LinkList()
        {
            head = null;
        }
        //往单链表末尾添加数据
        public void Add(int item)
        {
            Node newNode = new Node(item);
            if(head == null)
            {
                head.Next = newNode;
                return;
            }
            Node temp = head;
            while (temp.Next != null)
            {
                temp = temp.Next;
            }
            temp.Next = newNode;
        }
        //插入数据
        public void Insert(int item,int index)
        {
            Node newNode = new Node(item);
            if(index == 0)//插入到头节点
            {
                newNode.Next = head;
                head = newNode;
            }
            else
            {
                Node temp = head;
                for(int i = 1;i<=index-1;i++)
                {
                    temp = temp.Next;
                }
                Node currentNode = temp.Next;
                temp.Next = newNode;
                newNode.Next = currentNode;
            }
        }
        //删除数据
        public int Delete(int index)
        {
            int data;
            if(index == 0)
            {
                data = head.Data;
                head = head.Next;
            }
            else
            {
                Node temp = head;
                for(int i = 1;i<=index-1;i++)
                {
                    temp = temp.Next;
                }
                data = temp.Next.Data;
                temp.Next = temp.Next.Next;
            }
            return data;
        }
        //得到单链表长度
        public int GetLength()
        {
            if(head == null)
            {
                return 0;
            }
            Node temp = head;
            int count = 0;
            while(true)
            {
                if(temp.Next != null)
                {
                    count++;
                    temp = temp.Next;
                }
                else
                {
                    break;
                }
            }
            return count;
        }
        //清空单链表
        public void Clear()
        {
            head = null;
        }
        //判断链表是否为空
        public bool IsEmpty()
        {
            return head == null;
        }
        //传位置,得数据
        public int this[int index]
        {
            get
            {
                Node temp = head;
                for(int i = 1;i<=index;i++)
                {
                    temp = temp.Next;
                }
                return temp.Data;
            }
        }
        public int GetEle(int index)
        {
            return this[index];
        }
        //传数据,得位置
        public int Locate(int value)
        {
            Node temp = head;
            if(head == null)
            {
                return -1;
            }
            else
            {
                int index = 0;
                while(true)
                {
                    if(temp.Data == value)
                    {
                        return index;
                    }
                    else
                    {
                        if(temp.Next == null)
                        {
                            break;
                        }
                        temp = temp.Next;
                        index++;
                    }
                }
                return -1;
            }
        }
    }
}

对单链表中每个方法的解释:

1)单链表末尾添加数据

Add(单链表末尾添加数据):首先你得把数据放在一个节点里。然后只需要把最后一个节点中的next指向需要添加的节点。当然,你还需要考虑头节点为空的情况为什么需要声明一个temp,因为你不能让头节点去往后移动遍历(头节点不能动啊!动了之后头节点值不就变了,数据不就乱了),这时候就需要一个temp,了解一点内存储存的知识,应该就能很好的理解(大概讲一讲,就是head指向一个地址用来存放数据,现在让temp=head,也就是让temp也指向这个地址)。

下面这种是正确写法,但是你如果把if语句和里面的内容全删了,你猛地一看,你会不会感觉这好像看着逻辑也没问题。其实问题很大,因为head为null,那么你temp=head,temp是不是也为null,最后你temp里不是空了,那你想想,head里是不是还是空的。所以必须考虑head为空的情况。

        public void Add(int item)
        {
            Node newNode = new Node(item);
            if(head == null)
            {
                head.Next = newNode;
                return;
            }
            Node temp = head;
            while (temp.Next != null)
            {
                temp = temp.Next;
            }
            temp.Next = newNode;
        }

2)往单链表里面插入数据

Insert(往单链表里面插入数据):当把节点(蓝框)插入头节点的位置时

 思路就是让head指向插入的节点(这里的head=插入的节点,意思差不多就是把新插入节点的名字变成head了),让插入节点的next指向原来的头节点后面的节点(以后不管进行啥操作,都是从头节点开始遍历的,直接把头节点的指向变一下,相当于原来那个数据就没有了): 

当把蓝框插入中间某个位置时,直接看图理解吧,看图更容易理解:

注意一个细节:写代码时记得先把后面那个节点存起来先,要不你让前面那个节点的next指向蓝框时,那后面那个节点你就找不到了。

当把蓝框插入最后一个位置时:其实你会发现这和上面差不多,只不过后面那个节点变为空了,所以这两种情况可以用一种情况来写。

 代码如下:

        public void Insert(int item,int index)
        {
            Node newNode = new Node(item);
            if(index == 0)//插入到头节点
            {
                newNode.Next = head;
                head = newNode;
            }
            else
            {
                Node temp = head;
                for(int i = 1;i<=index-1;i++)
                {
                    temp = temp.Next;
                }
                Node currentNode = temp.Next;
                temp.Next = newNode;
                newNode.Next = currentNode;
            }
        }

 当然这代码里还有bug,当传入的index为负数或者一个大的数(一共就6个节点,他让你往第10个节点的地方插入节点),你就没法插入,你需要报错一下,自己知道就行,想写的也可以自己写写。

3)删除链表里某个元素并返回删除的元素

delete(删除某个元素):分两种情况,第一种情况,如果删除的时头节点:

只需要直接让head指向第二个节点就行(这样好理解:也就是直接把第二个节点名字改成head)。(以后不管进行啥操作,都是从头节点开始遍历的,直接把头节点的指向变一下,相当于原来那个数据就没有了)

第二种情况(删除中间的节点和删除最后一个节点可以归为一种情况):

先遍历到要删除元素的前一个节点(a),然后直接让a的next指向他后面的后面那个节点。删除方法需要返回被删除的元素,记得先把需要删除的那个值存起来,要不删完就找不到了。

 代码如下:

        public int Delete(int index)
        {
            int data;
            if(index == 0)
            {
                data = head.Data;
                head = head.Next;
            }
            else
            {
                Node temp = head;
                for(int i = 1;i<=index-1;i++)
                {
                    temp = temp.Next;
                }
                data = temp.Next.Data;
                temp.Next = temp.Next.Next;
            }
            return data;
        }

当然这里面还有bug,当他传入的index是负数或者比节点数多的数,就无法删除,你需要把这两种情况考虑一下,需要去报错一下,自己想写就自己写写。

4)返回单链表的长度

GetLength(返回单链表的长度):这个应该没啥难度吧,就遍历一下就行。

        public int GetLength()
        {
            if(head == null)
            {
                return 0;
            }
            Node temp = head;
            int count = 0;
            while(true)
            {
                if(temp.Next != null)
                {
                    count++;
                    temp = temp.Next;
                }
                else
                {
                    break;
                }
            }
            return count;
        }

5)清空单链表

Clear(清空单链表):只需要让头节点为空就行,那链表后面的节点就会慢慢的被垃圾回收器自动回收。

        public void Clear()
        {
            head = null;
        }

6)判断单链表是否为空

IsEmpty(判断单链表是否为空):没啥可解释的

        public bool IsEmpty()
        {
            return head == null;
        }

7)传入位置,返回数据

这里我们用索引器来实现。也可以不用索引器,反正道理都一样,都是往后遍历就行,没啥难度。

        public int this[int index]
        {
            get
            {
                Node temp = head;
                for(int i = 1;i<=index;i++)
                {
                    temp = temp.Next;
                }
                return temp.Data;
            }
        }
        public int GetEle(int index)
        {
            return this[index];
        }

8)传入数据,返回位置

Locate(传入数据,返回位置):没啥难度,还是遍历,然后找位置就ok。(找不到就返回-1)

        public int Locate(int value)
        {
            Node temp = head;
            if(head == null)
            {
                return -1;
            }
            else
            {
                int index = 0;
                while(true)
                {
                    if(temp.Data == value)
                    {
                        return index;
                    }
                    else
                    {
                        if(temp.Next == null)
                        {
                            break;
                        }
                        temp = temp.Next;
                        index++;
                    }
                }
                return -1;
            }

三、双向链表和循环链表(简单提一下)

1.双向链表

就是让节点里多了一个prev,用来指向前一个节点。

 

2.循环链表

在单链表的基础上,让最后一个节点的next指向第一个节点。

 

  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超02

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值