zoukankan      html  css  js  c++  java
  • 第五节:链表剖析及自己手撸"单链表"实现基本操作(初始化、增、删、改等)

    一. 基础

    1. 前言

      链式存储结构,又叫链表,逻辑上相邻,但是物理位置可以不相邻,因此对链表进行插入和删除时不需要移动数据元素,但是存取数据的效率却很低,分为三类:

    (1).单(向)链表:存储数据元素时,除了存储数据元素本身的信息外,还有一个指针,指向他的后继节点,最后一个元素的指针不指向任何元素。

    【C#中的代表:ListDictionary,基本上已经被弃用了,官方称建议存10个元素以下,很鸡肋】

    (2).双(向)链表:在单链表的基础上加一个前驱指针,指向前一个元素,是链表可以双向查找,这种结构成为双链表。

    【C#中的代表:LinkedList<T>】

    (3).循环链表:将链表最后一个节点的指针指向第一个节点,这样就形成了一个环,这种数据结构叫做单向循环列表.另外还有双向循环列表.

    PS:循环链表结构中从表的任一节点出发均可以找到表中的其他节点如果从表头节点出发,访问链表的最后一个节点,必须扫描表中所有节点。若循环链表的头指 针改用尾指针代替,则从尾指针出发不仅可以立即访问到最后一个节点,而且十分方便的找到第一个节点。

    2. LinkedList<T>核心分析

      首先LinkedList是一个双向链表!!,节点的存储类型为LinkedListNode<T>,可以通过node.Next和node.Previous获取下一个节点或前一个节点,添加节点的时候主要是基于 一个节点往前加或者往后加(AddBefore,AddAfter).

    (1).增加:AddFirst、AddLast、AddBefore、AddAfter

    (2).删除:Remove、RemoveFirst、RemoveLast; 其中Remove是根据值反删节点(内部循环,慢啊),也可以查出节点进行删除

    (3).修改: node.Value = xxx;

    (4).查询:

      A. 获取第1个/最后一个节点:First 、Last (.Value 就获取值了)

      B. 根据值反查节点: Find、FindLast (内部遍历,慢啊,链表的通病,查询慢)

      C. 获取下一个节点: node.Next (node.Next.Value; 获取值了)

      D. 获取前一个节点: node.Previous (node.Previous.Value; 获取值了)

      E. 输出所有值: Foreach

    (5).其它:Contains、Count

    代码分享:

     1 {
     2                 LinkedList<int> list1 = new LinkedList<int>();
     3                 list1.AddFirst(1);
     4                 list1.AddLast(10);
     5                 //找到节点
     6                 LinkedListNode<int> node1 = list1.Find(10);
     7                 Console.WriteLine($"node1的节点的值为:{node1.Value}");
     8                 list1.AddBefore(node1, 9);
     9                 list1.AddAfter(node1, 11);
    10 
    11                 //此时在获取node1的前一个节点和后一个节点
    12                 var node1Pre = node1.Previous;
    13                 Console.WriteLine($"node1的前一个节点的值为:{node1Pre.Value}");
    14                 var node1Next = node1.Next;
    15                 Console.WriteLine($"node1的的后一个节点的值为:{node1Next.Value}");
    16 
    17                 //修改节点的值
    18                 node1Next.Value = 111;
    19 
    20                 //继续增加
    21                 list1.AddLast(14);
    22                 list1.AddLast(15);
    23                 list1.AddLast(16);
    24                 list1.AddLast(17);
    25                 list1.AddLast(18);
    26 
    27                 //删除节点
    28                 list1.Remove(16);
    29                 var dNode = list1.Find(17);
    30                 list1.Remove(dNode);
    31                 //删除最后一个节点
    32                 list1.RemoveLast();
    33 
    34                 Console.WriteLine("全部输出");
    35                 foreach (var item in list1)
    36                 {
    37                     Console.WriteLine(item);
    38                 }
    39             }
    View Code

    运行结果:

     

    二. 提高

    1.手撸单链表

     (1).首先要有一个节点类Node,需要能记录该节点的值Data,并且根据该节点能获取下一个节点Next.实例化的时候可以传值,也可以不传值

     (2).要有一个头结点作为基础、节点的个数;实例化的时候可以传值,也可以不传值.

     (3).先要编写一个方法GetNodeByIndex,根据索引来获取节点,实质上只能遍历,其它方法基本上都要基于它.

     (4).然后实现Add、Insert、RemoveAt、this[int index]、ToString等方法,需要注意的是越界问题、index=0问题、头结点为空的问题.

    最终达到:获取一个节点,可以通过Data获取节点值,通过Next获取下一个节点,然后可以增删改查.

    PS:单链表也好,双链表也罢,只要不是循环列表,尾节点的指针都是指向null.

    Node节点代码

        /// <summary>
        /// 节点类
        /// </summary>
        public class Node<T>
        {
            public T _data;  // 存放节点数据
            public Node<T> _next; //存放下一个节点
    
            public Node()
            {
    
            }
            public Node(T data)
            {
                _data = data;
            }
    
            /// <summary>
            /// 数据的输出
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return _data.ToString();
            }
    
            //当前节点的数据
            public T Data
            {
                get
                {
                    return _data;
                }
                set
                {
                    _data = value;
                }
            }
            //下一个节点
            public Node<T> Next
            {
                get
                {
                    return _next;
                }
                set
                {
                    _next = value;
                }
            }
        }
    View Code

    单链表代码

      1 /// <summary>
      2     /// 手写单链表
      3     /// </summary>
      4     public class MySingleLinkedList<T>
      5     {
      6         private Node<T> _header;  //头结点
      7         private int _count;  //元素个数
      8         public MySingleLinkedList()
      9         {
     10 
     11         }
     12         public MySingleLinkedList(T data)
     13         {
     14             _header = new Node<T>(data);
     15             _count++;
     16         }
     17         //只能读
     18         public int Count
     19         {
     20             get
     21             {
     22                 return _count;
     23             }       
     24         }
     25         /// <summary>
     26         /// 根据索引获取元素
     27         /// </summary>
     28         /// <param name="index"></param>
     29         /// <returns></returns>
     30         public Node<T> GetNodeByIndex(int index)
     31         {
     32             if (index < 0 || index>=_count)
     33             {
     34                 throw new ArgumentOutOfRangeException("索引越界");
     35             }
     36             Node<T> tempNode = _header;
     37             //当index为0的时候,不进入for循环
     38             for (int i = 0; i < index; i++)
     39             {
     40                 tempNode = tempNode.Next;
     41             }
     42             return tempNode;
     43         }
     44 
     45         //索引器获取和设置数据
     46         public T this[int index]
     47         {
     48             get
     49             {
     50                 return GetNodeByIndex(index).Data;
     51             }
     52             set
     53             {
     54                 GetNodeByIndex(index).Data = value;
     55             }
     56         }
     57 
     58         /// <summary>
     59         /// 添加元素(最后)
     60         /// </summary>
     61         /// <param name="data"></param>
     62         public void Add(T data)
     63         {
     64             Node<T> newNode = new Node<T>(data);
     65             if (_header==null)  //表示添加的是第一个节点
     66             {
     67                 _header = newNode;
     68             }
     69             else
     70             {
     71                 var lastNode = GetNodeByIndex(_count - 1);
     72                 lastNode.Next = newNode;
     73             }       
     74             _count++;
     75         }
     76 
     77         /// <summary>
     78         /// 插入元素
     79         /// </summary>
     80         /// <param name="index">索引</param>
     81         /// <param name="data">数据</param>
     82         public void Insert(int index,T data)
     83         {
     84             if (index < 0||index>_count)
     85             {
     86                 throw new ArgumentOutOfRangeException("索引越界");
     87             }
     88             var newNode = new Node<T>(data); //新节点
     89             if (index==0)
     90             {
     91                 //头结点为空,直接插入头结点即可
     92                 if (_header==null)
     93                 {
     94                     _header = newNode;
     95                 }
     96                 //头结点有元素,需要把头结点的位置让出来
     97                 else
     98                 {
     99                     newNode.Next = _header;
    100                     _header = newNode;
    101                 }
    102             }
    103             else
    104             {
    105                 var preNode = GetNodeByIndex(index-1);  //查找插入位置的前驱节点
    106                 var nextNode = preNode.Next;            //插入位置的后继节点
    107                 preNode.Next = newNode;        //前驱结点的后继节点为新节点
    108                 newNode.Next = nextNode;       //新节点的后继节点执行原来前驱的后继
    109             }
    110             _count++;             
    111         }
    112 
    113         /// <summary>
    114         /// 根据索引删除元素
    115         /// </summary>
    116         /// <param name="index">索引</param>
    117         public void RemoveAt(int index)
    118         {
    119             if (index < 0 || index >= _count)
    120             {
    121                 throw new ArgumentOutOfRangeException("索引越界");
    122             }
    123             if (index==0)  //删除头结点
    124             {
    125                 if (_header==null)
    126                 {
    127                     throw new ArgumentOutOfRangeException("索引越界");
    128                 }
    129                 _header = _header.Next;
    130             }
    131             else
    132             {
    133                 var preNode = GetNodeByIndex(index - 1); //删除节点的前驱节点
    134                 if (preNode.Next==null)
    135                 {
    136                     throw new ArgumentOutOfRangeException("索引越界");
    137                 }
    138                 preNode.Next = preNode.Next.Next;     //如果删除的是最后一个节点,那么它的前一个节点指向null
    139             }
    140             _count--;
    141         }
    142         /// <summary>
    143         /// 元素输出
    144         /// </summary>
    145         /// <returns></returns>
    146         public override string ToString()
    147         {
    148             string s = "";
    149             Node<T> temp = _header;
    150             while (temp != null)
    151             {
    152                 s += temp.ToString() + " ";
    153                 temp = temp.Next;
    154             }
    155             return s;
    156         }
    157 
    158     }
    View Code

    测试代码

     1             {
     2                 Console.WriteLine("--------------------------手撸单链表测试---------------------------");
     3                 MySingleLinkedList<int> mlist = new MySingleLinkedList<int>();
     4                 mlist.Add(0);
     5                 mlist.Add(1);
     6                 mlist.Add(3);
     7                 mlist.Add(4);
     8                 mlist.Add(5);
     9                 mlist.Add(6);
    10                 mlist.Insert(0, 100);
    11                 Console.WriteLine(mlist.ToString());
    12                 mlist.RemoveAt(4);
    13                 mlist[1] = 999;
    14                 Console.WriteLine(mlist.ToString());
    15                 for (int i = 0; i < mlist.Count; i++)
    16                 {
    17                     Console.WriteLine(mlist[i]);
    18                 }
    19                 mlist.Insert(5, 555);
    20                 Console.WriteLine(mlist.ToString());
    21                 mlist.RemoveAt(0);
    22                 Console.WriteLine(mlist.ToString());
    23             }

    运行结果

     

    2.链表优缺点

    优点

      (1).需要空间就申请,不需要就释放,空间有效利用。

      (2).插入和删除只需要修改几个指针,方便快捷。

      (3).双链表双向搜索,简化算法复杂度。

    缺点

      (1).算法实现复杂抽象,需要额外内存空间保存节点的逻辑关系。

      (2).只能顺序访问,访问速度慢。

      (3).可能会把节点存放到托管堆的任意角落,垃圾回收需要更多开销。

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    A Simple Problem About Truth Table
    Android Fragment完全解析,关于碎片你所需知道的一切
    Android系统中标准Intent的使用
    Android应用开发全程实录-你有多熟悉listView
    Android四大基本组件介绍及生命周期
    设计模式
    Java流(Stream)、文件(File)和IO
    Java网络编程详解
    Java 多线程
    深入理解Java虚拟机 精华总结(面试)
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/12705613.html
Copyright © 2011-2022 走看看