• 数据结构之链表


    引用B站离忧夏天的视频

    1.从动态数组的原理中我们知道,就算是使用动态数组,我们也还是未能完全利用全部的空间,所以可以使用链表实现。 

     数组链表区别

    从上图我们可以看出,数组在查询数据方面要比链表更快速,可以通过索引快速查询,但是链表不行,比如我要找3节点,就必须知道2节点,根据2节点找到下一节点3,同理一次向前推,一切的开始就必须要知道节点的最开始点,也就是头节点0才行。

    我们可以自己创建一个链表类,看一下具体实现原理:

    先开始实现一个简单的链表类,链表中存在很多节点,把这些节点抽象出类,节点类同时需要记录当前节点的信息以及下一节点的信息。链表类还要有头部节点的信息,以及知道当前总共的节点数量。

    在c#中LinkedList类是封装好的一个链表类,下面自定义的一个链表类和封装好的类名称一样,不要混了。 

    public class LinkedList<T>
        {
            //当前链表类记录这个链表的头部类
            private Node head;
            //链表的节点数量
            private int N;
            public LinkedList()
            {
                N = 0;
                //因为一开始链表没有元素,所以头部节点指向空。
                head = null;
            }
            /// <summary>
            /// 链表节点数量
            /// </summary>
            public int Count
            {
                get { return N; }
            }
            /// <summary>
            /// 链表是否为空
            /// </summary>
            public bool IsEmpty
            {
                get { return N == 0; }
            }
             
            /// <summary>
            /// 节点类  不让外部知道此类,设为私有
            /// </summary>
            private class Node
            {
                //当前节点
                public T e;
                //当前节点的下一节点
                public Node next;
                public Node(T e, Node next)
                {
                    this.e = e;
                    this.next = next;
                }
                public Node(T e)
                {
                    this.e = e;
                }
    
            }
    
    
        }

    下面实现往链表的头部插入元素:

     

    看上图是链表往头部添加节点的实现过程,链表和数组不同,数组添加元素是很麻烦的,需要对原有数据进行重新移位,而链表是只要其中的任意节点和另一节点有关联就可以,比如我要往头节点插入一个新节点,那么原先的头结点0就不是头节点,而5变成新的头结点,所以此时只需要将5节点的下一节点指向0,链表类中记录的头结点改成5即可,比数组快很多。

     下面是往中间或尾部插入节点:

    如图:将节点6插入到index=3的地方,也就是节点2与节点3之间的位置,需要知道前一节点2的信息,要知道2节点信息,对于链表来说就必须从头部节点一步一步的找,找到2节点之后,先将2节点存储的下一节点的信息给6节点,然后将6节点的信息给2,此时新的链表形成了,比数组便捷多了,数组还要对原有数据进行重新移位排序。当然查询数据的话数组更快,根据索引直接就能查到。

    代码实现:

       public class LinkedList<T>
        {
            //当前链表类记录这个链表的头部类
            private Node head;
            //链表的节点数量
            private int N;
            public LinkedList()
            {
                N = 0;
                //因为一开始链表没有元素,所以头部节点指向空。
                head = null;
            }
            /// <summary>
            /// 链表节点数量
            /// </summary>
            public int Count
            {
                get { return N; }
            }
            /// <summary>
            /// 链表是否为空
            /// </summary>
            public bool IsEmpty
            {
                get { return N == 0; }
            }
    
            public void Add(int index, T e)
            {
                if (index < 0 || index > N)
                {
                    throw new Exception("非法索引");
                }
    
                if (index == 0)
                {
                    //新的头部节点
                    //1:添加新节点
                    Node node = new Node(e);
                    //2:将新添加的头结点的箭头指向原先头结点
                    node.next = head;
                    //3:现在的头结点信息赋给类的头结点字段,记录下来。
                    head = node;
                    //其实上面三步可以结合到一步中,
                    head = new Node(e, head);
                }
                else
                {
                    //添加非头部节点
                    //1.需要找到插入位置的上一个节点,对于链表来说就要从头部节点开始一步一步的查找了
                    Node pre = head;
                    //一步一步遍历找到前一个节点信息
                    for (int i = 0; i < index - 1; i++)
                    {
                        pre = pre.next;
                    }
                    //1:将前一节点的下一节点信息赋值给当前节点对象的下一节点字段,目的是为了让当前节点能够链接到下一节点
                    Node node = new Node(e);
                    node.next = pre.next;
                    //2:前一节点链接到新插入的节点 此时链表已经重新完成
                    pre.next = node;
                    //上面代码也可以合成一句
                    pre.next = new Node(e, pre.next);
    
                }
                //链表的元素加一
                N++;  
            }
    
            public void AddFirst(T e)
            {
                Add(0,e);
            }
    
            public void AddLast(T e)
            {
                Add(N, e);
            }
    
            /// <summary>
            /// 节点类  不让外部知道此类,设为私有
            /// </summary>
            private class Node
            {
                //当前节点
                public T e;
                //当前节点的下一节点
                public Node next;
                public Node(T e, Node next)
                {
                    this.e = e;
                    this.next = next;
                }
                public Node(T e)
                {
                    this.e = e;
                }
    
            }
    
    
        }

    然后我们需要实现查询节点信息功能,链表中查询某个节点信息需要从头结点一步一步往后查找, 

    public class LinkedList<T>
        {
            //当前链表类记录这个链表的头部类
            private Node head;
            //链表的节点数量
            private int N;
            public LinkedList()
            {
                N = 0;
                //因为一开始链表没有元素,所以头部节点指向空。
                head = null;
            }
            /// <summary>
            /// 链表节点数量
            /// </summary>
            public int Count
            {
                get { return N; }
            }
            /// <summary>
            /// 链表是否为空
            /// </summary>
            public bool IsEmpty
            {
                get { return N == 0; }
            }
    
            public void Add(int index, T e)
            {
                if (index < 0 || index > N)
                {
                    throw new Exception("非法索引");
                }
    
                if (index == 0)
                {
                    //新的头部节点
                    //1:添加新节点
                    //Node node = new Node(e);
                    ////2:将新添加的头结点的箭头指向原先头结点
                    //node.next = head;
                    ////3:现在的头结点信息赋给类的头结点字段,记录下来。
                    //head = node;
                    //其实上面三步可以结合到一步中,
                    head = new Node(e, head);
                }
                else
                {
                    //添加非头部节点
                    //1.需要找到插入位置的上一个节点,对于链表来说就要从头部节点开始一步一步的查找了
                    Node pre = head;
                    //一步一步遍历找到前一个节点信息
                    for (int i = 0; i < index - 1; i++)
                    {
                        pre = pre.next;
                    }
                    //1:将前一节点的下一节点信息赋值给当前节点对象的下一节点字段,目的是为了让当前节点能够链接到下一节点
                    Node node = new Node(e);
                    node.next = pre.next;
                    //2:前一节点链接到新插入的节点 此时链表已经重新完成
                    pre.next = node;
                    //上面代码也可以合成一句
                 //   pre.next = new Node(e, pre.next);
    
                }
                //链表的元素加一
                N++;
            }
    
            public void AddFirst(T e)
            {
                Add(0, e);
            }
    
            public void AddLast(T e)
            {
                Add(N, e);
            }
    
            /// <summary>
            /// 节点类  不让外部知道此类,设为私有
            /// </summary>
            private class Node
            {
                //当前节点
                public T e;
                //当前节点的下一节点
                public Node next;
                public Node(T e, Node next)
                {
                    this.e = e;
                    this.next = next;
                }
                public Node(T e)
                {
                    this.e = e;
                }
    
            }
    
    
            public T Get(int index)
            {
                if (index < 0 || index >= N)
                {
                    throw new Exception("非法索引");
                }
    
                Node currentNode = head;//从头结点开始查找
                for (int i = 0; i < index; i++)
                {
                    currentNode = currentNode.next;
                }
    
                return currentNode.e;
            }
    
    
            public T GetFirst(int index)
            {
                return Get(0);
            }
            public T GetLast(int index)
            {
                return Get(N - 1);
            }
    
            /// <summary>
            /// 修改
            /// </summary>
            /// <param name="index"></param>
            /// <param name="e"></param>
            /// <returns></returns>
            public void Set(int index, T e)
            {
                if (index < 0 || index >= N)
                {
                    throw new Exception("非法索引");
                }
    
                Node currentNode = head;//从头结点开始查找
                for (int i = 0; i < index; i++)
                {
                    currentNode = currentNode.next;
                }
    
                currentNode.e = e; ;
            }
            /// <summary>
            /// 查找链表中是否存在此元素
            /// </summary>
            /// <param name="e"></param>
            /// <returns></returns>
            public bool Contains(T e)
            {
                Node current = head;
                while (current != null)
                {
                    if (current.e.Equals(e))
                    {
                        return true;
                    }
    
                    current = current.next;
                }
    
                return false;
            }
            /// <summary>
            /// 可以重写tostring打印方法
            /// 打印出链表信息
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                StringBuilder stringBuilder = new StringBuilder();
                Node cur = head;
                while (cur != null)
                {
                    stringBuilder.Append(cur.e + "->");
                    cur = cur.next;
                }
                stringBuilder.Append("null");
                return stringBuilder.ToString();
                 
            }
    
        }

    调用方法:

      
        class Program
        { 
            static void Main(string[] args)
            {
                LinkedList<int> linkedList = new LinkedList<int>(); 
                for (int i = 0; i <5; i++)
                {
                    linkedList.Add(i,i);
                }
             
                  Console.WriteLine(linkedList);
            } 
    
        }

    结果:

    0->1->2->3->4->null

    删除头部节点:如下图,要想删除头部节点5,只需要将头部节点5指向的下一节点设置成新的头部节点即可。

    删除中间或尾部节点:只需要将6节点独立出整个链表就可以,直接将1节点指向2节点这一步就可以。最终内存的管理系统会进行回收。

    删除指定节点,上面都是通过索引index来查找对应位置的数据,下面的情况是不知道具体位置,只知道节点信息来进行删除:

    如下图:比如说想要删除节点6,我们需要找到节点6本身以及节点6的上一个节点,更改上一个节点的指向信息,也就是说更改让1节点与2节点关联,此时6节点就脱离出来,完成删除。

     代码实现:

      public class MyLinkedList<T>
        {
            //当前链表类记录这个链表的头部类
            private Node head;
            //链表的节点数量
            private int N;
            public MyLinkedList()
            {
                N = 0;
                //因为一开始链表没有元素,所以头部节点指向空。
                head = null;
            }
            /// <summary>
            /// 链表节点数量
            /// </summary>
            public int Count
            {
                get { return N; }
            }
            /// <summary>
            /// 链表是否为空
            /// </summary>
            public bool IsEmpty
            {
                get { return N == 0; }
            }
    
            public void Add(int index, T e)
            {
                if (index < 0 || index > N)
                {
                    throw new Exception("非法索引");
                }
    
                if (index == 0)
                {
                    //新的头部节点
                    //1:添加新节点
                    //Node node = new Node(e);
                    ////2:将新添加的头结点的箭头指向原先头结点
                    //node.next = head;
                    ////3:现在的头结点信息赋给类的头结点字段,记录下来。
                    //head = node;
                    //其实上面三步可以结合到一步中,
                    head = new Node(e, head);
                }
                else
                {
                    //添加非头部节点
                    //1.需要找到插入位置的上一个节点,对于链表来说就要从头部节点开始一步一步的查找了
                    Node pre = head;
                    //一步一步遍历找到前一个节点信息
                    for (int i = 0; i < index - 1; i++)
                    {
                        pre = pre.next;
                    }
                    //1:将前一节点的下一节点信息赋值给当前节点对象的下一节点字段,目的是为了让当前节点能够链接到下一节点
                    Node node = new Node(e);
                    node.next = pre.next;
                    //2:前一节点链接到新插入的节点 此时链表已经重新完成
                    pre.next = node;
                    //上面代码也可以合成一句
                    //   pre.next = new Node(e, pre.next);
    
                }
                //链表的元素加一
                N++;
            }
    
            public void AddFirst(T e)
            {
                Add(0, e);
            }
    
            public void AddLast(T e)
            {
                Add(N, e);
            }
    
            /// <summary>
            /// 节点类  不让外部知道此类,设为私有
            /// </summary>
            private class Node
            {
                //当前节点
                public T e;
                //当前节点的下一节点
                public Node next;
                public Node(T e, Node next)
                {
                    this.e = e;
                    this.next = next;
                }
                public Node(T e)
                {
                    this.e = e;
                }
    
            }
    
    
            public T Get(int index)
            {
                if (index < 0 || index >= N)
                {
                    throw new Exception("非法索引");
                }
    
                Node currentNode = head;//从头结点开始查找
                for (int i = 0; i < index; i++)
                {
                    currentNode = currentNode.next;
                }
    
                return currentNode.e;
            }
    
    
            public T GetFirst(int index)
            {
                return Get(0);
            }
            public T GetLast(int index)
            {
                return Get(N - 1);
            }
    
            /// <summary>
            /// 修改
            /// </summary>
            /// <param name="index"></param>
            /// <param name="e"></param>
            /// <returns></returns>
            public void Set(int index, T e)
            {
                if (index < 0 || index >= N)
                {
                    throw new Exception("非法索引");
                }
    
                Node currentNode = head;//从头结点开始查找
                for (int i = 0; i < index; i++)
                {
                    currentNode = currentNode.next;
                }
    
                currentNode.e = e; ;
            }
            /// <summary>
            /// 查找链表中是否存在此元素
            /// </summary>
            /// <param name="e"></param>
            /// <returns></returns>
            public bool Contains(T e)
            {
                Node current = head;
                while (current != null)
                {
                    if (current.e.Equals(e))
                    {
                        return true;
                    }
    
                    current = current.next;
                }
    
                return false;
            }
            /// <summary>
            /// 可以重写tostring打印方法
            /// 打印出链表信息
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                StringBuilder stringBuilder = new StringBuilder();
                Node cur = head;
                while (cur != null)
                {
                    stringBuilder.Append(cur.e + "->");
                    cur = cur.next;
                }
                stringBuilder.Append("null");
                return stringBuilder.ToString();
    
            }
    
            #region 删除节点
    
            public T Remove(int index)
            {
                if (index < 0 || index > N)
                {
                    throw new Exception("非法索引");
                }
    
                if (index == 0)
                {
                    Node delNode = head;
                    //直接将原头部节点指向的下一节点的信息存储到头部节点的字段上,直接这样就能删除原头部节点了
                    head = delNode.next;
                    N--;
                    return delNode.e;
                }
                else
                {
                    Node pre = head;
                    //找到删除节点的前一节点信息
                    for (int i = 0; i < index - 1; i++)
                    {
                        pre = pre.next;
                    }
                    Node delNode = pre.next;
                    //1:z将前一节点的指向做更改
                    pre.next = delNode.next;
                    N--;
                    return delNode.e;
                }
            }
    
            #endregion
            #region 删除节点  根据值节点查找
    
            public void Remove(T e)
            {
                if (head == null)
                {
                    return;
                }
    
                if (head.e.Equals(e))
                {
                    //删除的是头结点
                    head = head.next;
                    N--;
                }
                else
                {
                    Node current = head;
                    Node pre = null;
                    while (current != null)
                    {
                        if (current.e.Equals(e))
                        {
                            break;
                        }
    
                        pre = current;
                        current = current.next;
    
                    }
    
                    if (current != null)
                    {
                        pre.next = current.next.next;
                        N--; 
                    }
                }
    
            }
    
            #endregion
        }

    时间复杂度分析:下面简单介绍了运行时间主要影响因素。

    下图展示了数组复杂度的分析:

  • 相关阅读:
    《JavaScript语言精粹》小记
    JavaScript之单例实战
    浅谈requireJS
    细说gulp
    Javascript之自定义事件
    ClipboardJS复制粘贴插件的使用
    重新学习vue基础
    正则简单说明
    JavaScript字符串api简单说明
    移动端浏览器问题
  • 原文地址:https://www.cnblogs.com/anjingdian/p/15171993.html
Copyright © 2020-2023  润新知