• 算法系列15天速成——第八天 线性表【下】


    一:线性表的简单回顾

           上一篇跟大家聊过“线性表"顺序存储,通过实验,大家也知道,如果我每次向

    顺序表的头部插入元素,都会引起痉挛,效率比较低下,第二点我们用顺序存储时,容

    易受到长度的限制,反之就会造成空间资源的浪费。

    二:链表

          对于顺序表存在的若干问题,链表都给出了相应的解决方案。

    1. 概念:其实链表的“每个节点”都包含一个”数据域“和”指针域“。

                ”数据域“中包含当前的数据。

                ”指针域“中包含下一个节点的指针。

                ”头指针”也就是head,指向头结点数据。

                “末节点“作为单向链表,因为是最后一个节点,通常设置指针域为null。

    代码段如下:

     1     #region 链表节点的数据结构
    2 /// <summary>
    3 /// 链表节点的数据结构
    4 /// </summary>
    5 public class Node<T>
    6 {
    7/// <summary>
    8 /// 节点的数据域
    9 /// </summary>
    10 public T data;
    11
    12 /// <summary>
    13 /// 节点的指针域
    14 /// </summary>
    15 public Node<T> next;
    16 }
    17 #endregion


    2.常用操作:

        链表的常用操作一般有:

               ①添加节点到链接尾,②添加节点到链表头,③插入节点。

               ④删除节点,⑤按关键字查找节点,⑥取链表长度。

       

    <1> 添加节点到链接尾:

              前面已经说过,链表是采用指针来指向下一个元素,所以说要想找到链表最后一个节点,

           必须从头指针开始一步一步向后找,少不了一个for循环,所以时间复杂度为O(N)。

    代码段如下:

     1 #region 将节点添加到链表的末尾
    2 /// <summary>
    3 /// 将节点添加到链表的末尾
    4 /// </summary>
    5 /// <typeparam name="T"></typeparam>
    6 /// <param name="head"></param>
    7 /// <param name="data"></param>
    8 /// <returns></returns>
    9 public Node<T> ChainListAddEnd<T>(Node<T> head, T data)
    10 {
    11 Node<T> node = new Node<T>();
    12
    13 node.data = data;
    14 node.next = null;
    15
    16 ///说明是一个空链表
    17 if (head == null)
    18 {
    19 head = node;
    20 return head;
    21 }
    22
    23 //获取当前链表的最后一个节点
    24 ChainListGetLast(head).next = node;
    25
    26 return head;
    27 }
    28 #endregion
    29 #region 得到当前链表的最后一个节点
    30 /// <summary>
    31 /// 得到当前链表的最后一个节点
    32 /// </summary>
    33 /// <typeparam name="T"></typeparam>
    34 /// <param name="head"></param>
    35 /// <returns></returns>
    36 public Node<T> ChainListGetLast<T>(Node<T> head)
    37 {
    38 if (head.next == null)
    39 return head;
    40 return ChainListGetLast(head.next);
    41 }
    42 #endregion

    <2> 添加节点到链表头:

              大家现在都知道,链表是采用指针指向的,要想将元素插入链表头,其实还是很简单的,

          思想就是:① 将head的next指针给新增节点的next。②将整个新增节点给head的next。

          所以可以看出,此种添加的时间复杂度为O(1)。

    效果图:

    代码段如下:

     1#region 将节点添加到链表的开头
    2 /// <summary>
    3 /// 将节点添加到链表的开头
    4 /// </summary>
    5 /// <typeparam name="T"></typeparam>
    6 /// <param name="chainList"></param>
    7 /// <param name="data"></param>
    8 /// <returns></returns>
    9 public Node<T> ChainListAddFirst<T>(Node<T> head, T data)
    10 {
    11 Node<T> node = new Node<T>();
    12
    13 node.data = data;
    14 node.next = head;
    15
    16 head = node;
    17
    18 return head;
    19
    20 }
    21 #endregion


    <3> 插入节点:

               其实这个思想跟插入到”首节点“是一个模式,不过多了一步就是要找到当前节点的操作。然后找到

          这个节点的花费是O(N)。上图上代码,大家一看就明白。

    效果图:

    代码段:

     1 #region 将节点插入到指定位置
    2 /// <summary>
    3 /// 将节点插入到指定位置
    4 /// </summary>
    5 /// <typeparam name="T"></typeparam>
    6 /// <param name="head"></param>
    7 /// <param name="currentNode"></param>
    8 /// <param name="data"></param>
    9 /// <returns></returns>
    10 public Node<T> ChainListInsert<T, W>(Node<T> head, string key, Func<T, W> where, T data) where W : IComparable
    11 {
    12 if (head == null)
    13 return null;
    14
    15 if (where(head.data).CompareTo(key) == 0)
    16 {
    17 Node<T> node = new Node<T>();
    18
    19 node.data = data;
    20
    21 node.next = head.next;
    22
    23 head.next = node;
    24 }
    25
    26 ChainListInsert(head.next, key, where, data);
    27
    28 return head;
    29 }
    30 #endregion

    <4> 删除节点:

            这个也比较简单,不解释,图跟代码更具有说服力,口头表达反而让人一头雾水。

            当然时间复杂度就为O(N),N是来自于查找到要删除的节点。

    效果图:

    代码段:

     1 #region 将指定关键字的节点删除
    2 /// <summary>
    3 /// 将指定关键字的节点删除
    4 /// </summary>
    5 /// <typeparam name="T"></typeparam>
    6 /// <typeparam name="W"></typeparam>
    7 /// <param name="head"></param>
    8 /// <param name="key"></param>
    9 /// <param name="where"></param>
    10 /// <param name="data"></param>
    11 /// <returns></returns>
    12 public Node<T> ChainListDelete<T, W>(Node<T> head, string key, Func<T, W> where) where W : IComparable
    13 {
    14 if (head == null)
    15 return null;
    16
    17 //这是针对只有一个节点的解决方案
    18 if (where(head.data).CompareTo(key) == 0)
    19 {
    20 if (head.next != null)
    21 head = head.next;
    22 else
    23 return head = null;
    24 }
    25 else
    26 {
    27 //判断一下此节点是否是要删除的节点的前一节点
    28 while (head.next != null && where(head.next.data).CompareTo(key) == 0)
    29 {
    30 //将删除节点的next域指向前一节点
    31 head.next = head.next.next;
    32 }
    33 }
    34
    35 ChainListDelete(head.next, key, where);
    36
    37 return head;
    38 }
    39 #endregion



    <5> 按关键字查找节点:

             这个思想已经包含到“插入节点”和“删除节点”的具体运用中的,其时间复杂度为O(N)。

    代码段:

     1 #region 通过关键字查找指定的节点
    2 /// <summary>
    3 /// 通过关键字查找指定的节点
    4 /// </summary>
    5 /// <typeparam name="T"></typeparam>
    6 /// <typeparam name="W"></typeparam>
    7 /// <param name="head"></param>
    8 /// <param name="key"></param>
    9 /// <param name="where"></param>
    10 /// <returns></returns>
    11 public Node<T> ChainListFindByKey<T, W>(Node<T> head, string key, Func<T, W> where) where W : IComparable
    12 {
    13 if (head == null)
    14 return null;
    15
    16 if (where(head.data).CompareTo(key) == 0)
    17 return head;
    18
    19 return ChainListFindByKey<T, W>(head.next, key, where);
    20 }
    21 #endregion


    <6> 取链表长度:

              在单链表的操作中,取链表长度还是比较纠结的,因为他不像顺序表那样是在内存中连续存储的,

          因此我们就纠结的遍历一下链表的总长度。时间复杂度为O(N)。

    代码段:

     1         #region 获取链表的长度
    2 /// <summary>
    3 ///// 获取链表的长度
    4 /// </summary>
    5 /// <typeparam name="T"></typeparam>
    6 /// <param name="head"></param>
    7 /// <returns></returns>
    8 public int ChanListLength<T>(Node<T> head)
    9 {
    10 int count = 0;
    11
    12 while (head != null)
    13 {
    14 ++count;
    15 head = head.next;
    16 }
    17
    18 return count;
    19 }
    20 #endregion

    好了,最后上一下总的运行代码:

    View Code
      1 using System;
    2 using System.Collections.Generic;
    3 using System.Linq;
    4 using System.Text;
    5
    6 namespace ChainList
    7 {
    8 class Program
    9 {
    10 static void Main(string[] args)
    11 {
    12 ChainList chainList = new ChainList();
    13
    14 Node<Student> node = null;
    15
    16 Console.WriteLine("将三条数据添加到链表的尾部:\n");
    17
    18 //将数据添加到链表的尾部
    19 node = chainList.ChainListAddEnd(node, new Student() { ID = 2, Name = "hxc520", Age = 23 });
    20 node = chainList.ChainListAddEnd(node, new Student() { ID = 3, Name = "博客园", Age = 33 });
    21 node = chainList.ChainListAddEnd(node, new Student() { ID = 5, Name = "一线码农", Age = 23 });
    22
    23 Dispaly(node);
    24
    25 Console.WriteLine("将ID=1的数据插入到链表开头:\n");
    26
    27 //将ID=1的数据插入到链表开头
    28 node = chainList.ChainListAddFirst(node, new Student() { ID = 1, Name = "i can fly", Age = 23 });
    29
    30 Dispaly(node);
    31
    32 Console.WriteLine("查找Name=“一线码农”的节点\n");
    33
    34 //查找Name=“一线码农”的节点
    35 var result = chainList.ChainListFindByKey(node, "一线码农", i => i.Name);
    36
    37 DisplaySingle(node);
    38
    39 Console.WriteLine("将”ID=4“的实体插入到“博客园”这个节点的之后\n");
    40
    41 //将”ID=4“的实体插入到"博客园"这个节点的之后
    42 node = chainList.ChainListInsert(node, "博客园", i => i.Name, new Student() { ID = 4, Name = "51cto", Age = 30 });
    43
    44 Dispaly(node);
    45
    46 Console.WriteLine("删除Name=‘51cto‘的节点数据\n");
    47
    48 //删除Name=‘51cto‘的节点数据
    49 node = chainList.ChainListDelete(node, "51cto", i => i.Name);
    50
    51 Dispaly(node);
    52
    53 Console.WriteLine("获取链表的个数:" + chainList.ChanListLength(node));
    54 }
    55
    56 //输出数据
    57 public static void Dispaly(Node<Student> head)
    58 {
    59 Console.WriteLine("******************* 链表数据如下 *******************");
    60 var tempNode = head;
    61
    62 while (tempNode != null)
    63 {
    64 Console.WriteLine("ID:" + tempNode.data.ID + ", Name:" + tempNode.data.Name + ",Age:" + tempNode.data.Age);
    65 tempNode = tempNode.next;
    66 }
    67
    68 Console.WriteLine("******************* 链表数据展示完毕 *******************\n");
    69 }
    70
    71 //展示当前节点数据
    72 public static void DisplaySingle(Node<Student> head)
    73 {
    74 if (head != null)
    75 Console.WriteLine("ID:" + head.data.ID + ", Name:" + head.data.Name + ",Age:" + head.data.Age);
    76 else
    77 Console.WriteLine("未查找到数据!");
    78 }
    79 }
    80
    81 #region 学生数据实体
    82 /// <summary>
    83 /// 学生数据实体
    84 /// </summary>
    85 public class Student
    86 {
    87 public int ID { get; set; }
    88
    89 public string Name { get; set; }
    90
    91 public int Age { get; set; }
    92 }
    93 #endregion
    94
    95 #region 链表节点的数据结构
    96 /// <summary>
    97 /// 链表节点的数据结构
    98 /// </summary>
    99 public class Node<T>
    100 {
    101 /// <summary>
    102 /// 节点的数据域
    103 /// </summary>
    104 public T data;
    105
    106 /// <summary>
    107 /// 节点的指针域
    108 /// </summary>
    109 public Node<T> next;
    110 }
    111 #endregion
    112
    113 #region 链表的相关操作
    114 /// <summary>
    115 /// 链表的相关操作
    116 /// </summary>
    117 public class ChainList
    118 {
    119 #region 将节点添加到链表的末尾
    120 /// <summary>
    121 /// 将节点添加到链表的末尾
    122 /// </summary>
    123 /// <typeparam name="T"></typeparam>
    124 /// <param name="head"></param>
    125 /// <param name="data"></param>
    126 /// <returns></returns>
    127 public Node<T> ChainListAddEnd<T>(Node<T> head, T data)
    128 {
    129 Node<T> node = new Node<T>();
    130
    131 node.data = data;
    132 node.next = null;
    133
    134 ///说明是一个空链表
    135 if (head == null)
    136 {
    137 head = node;
    138 return head;
    139 }
    140
    141 //获取当前链表的最后一个节点
    142 ChainListGetLast(head).next = node;
    143
    144 return head;
    145 }
    146 #endregion
    147
    148 #region 将节点添加到链表的开头
    149 /// <summary>
    150 /// 将节点添加到链表的开头
    151 /// </summary>
    152 /// <typeparam name="T"></typeparam>
    153 /// <param name="chainList"></param>
    154 /// <param name="data"></param>
    155 /// <returns></returns>
    156 public Node<T> ChainListAddFirst<T>(Node<T> head, T data)
    157 {
    158 Node<T> node = new Node<T>();
    159
    160 node.data = data;
    161 node.next = head;
    162
    163 head = node;
    164
    165 return head;
    166
    167 }
    168 #endregion
    169
    170 #region 将节点插入到指定位置
    171 /// <summary>
    172 /// 将节点插入到指定位置
    173 /// </summary>
    174 /// <typeparam name="T"></typeparam>
    175 /// <param name="head"></param>
    176 /// <param name="currentNode"></param>
    177 /// <param name="data"></param>
    178 /// <returns></returns>
    179 public Node<T> ChainListInsert<T, W>(Node<T> head, string key, Func<T, W> where, T data) where W : IComparable
    180 {
    181 if (head == null)
    182 return null;
    183
    184 if (where(head.data).CompareTo(key) == 0)
    185 {
    186 Node<T> node = new Node<T>();
    187
    188 node.data = data;
    189
    190 node.next = head.next;
    191
    192 head.next = node;
    193 }
    194
    195 ChainListInsert(head.next, key, where, data);
    196
    197 return head;
    198 }
    199 #endregion
    200
    201 #region 将指定关键字的节点删除
    202 /// <summary>
    203 /// 将指定关键字的节点删除
    204 /// </summary>
    205 /// <typeparam name="T"></typeparam>
    206 /// <typeparam name="W"></typeparam>
    207 /// <param name="head"></param>
    208 /// <param name="key"></param>
    209 /// <param name="where"></param>
    210 /// <param name="data"></param>
    211 /// <returns></returns>
    212 public Node<T> ChainListDelete<T, W>(Node<T> head, string key, Func<T, W> where) where W : IComparable
    213 {
    214 if (head == null)
    215 return null;
    216
    217 //这是针对只有一个节点的解决方案
    218 if (where(head.data).CompareTo(key) == 0)
    219 {
    220 if (head.next != null)
    221 head = head.next;
    222 else
    223 return head = null;
    224 }
    225 else
    226 {
    227 //判断一下此节点是否是要删除的节点的前一节点
    228 if (head.next != null && where(head.next.data).CompareTo(key) == 0)
    229 {
    230 //将删除节点的next域指向前一节点
    231 head.next = head.next.next;
    232 }
    233 }
    234
    235 ChainListDelete(head.next, key, where);
    236
    237 return head;
    238 }
    239 #endregion
    240
    241 #region 通过关键字查找指定的节点
    242 /// <summary>
    243 /// 通过关键字查找指定的节点
    244 /// </summary>
    245 /// <typeparam name="T"></typeparam>
    246 /// <typeparam name="W"></typeparam>
    247 /// <param name="head"></param>
    248 /// <param name="key"></param>
    249 /// <param name="where"></param>
    250 /// <returns></returns>
    251 public Node<T> ChainListFindByKey<T, W>(Node<T> head, string key, Func<T, W> where) where W : IComparable
    252 {
    253 if (head == null)
    254 return null;
    255
    256 if (where(head.data).CompareTo(key) == 0)
    257 return head;
    258
    259 return ChainListFindByKey<T, W>(head.next, key, where);
    260 }
    261 #endregion
    262
    263 #region 获取链表的长度
    264 /// <summary>
    265 ///// 获取链表的长度
    266 /// </summary>
    267 /// <typeparam name="T"></typeparam>
    268 /// <param name="head"></param>
    269 /// <returns></returns>
    270 public int ChanListLength<T>(Node<T> head)
    271 {
    272 int count = 0;
    273
    274 while (head != null)
    275 {
    276 ++count;
    277 head = head.next;
    278 }
    279
    280 return count;
    281 }
    282 #endregion
    283
    284 #region 得到当前链表的最后一个节点
    285 /// <summary>
    286 /// 得到当前链表的最后一个节点
    287 /// </summary>
    288 /// <typeparam name="T"></typeparam>
    289 /// <param name="head"></param>
    290 /// <returns></returns>
    291 public Node<T> ChainListGetLast<T>(Node<T> head)
    292 {
    293 if (head.next == null)
    294 return head;
    295 return ChainListGetLast(head.next);
    296 }
    297 #endregion
    298
    299 }
    300 #endregion
    301 }

    运行结果:

    当然,单链表操作中有很多是O(N)的操作,这给我们带来了尴尬的局面,所以就有了很多的

    优化方案,比如:双向链表,循环链表。静态链表等等,这些希望大家在懂得单链表的情况下

    待深一步的研究。

  • 相关阅读:
    邻接矩阵学习
    Spark on Yarn年度知识整理
    HBASE解析
    Storm的数据处理编程单元:Bolt 学习整理
    jsch ssh服务器调用Linux命令或脚本的小问题
    storm的数据源编程单元Spout学习整理
    Java 正则表达式详细使用
    【转】如何成为一个技术全面的架构师
    【转】Linux netstat命令详解,高级面试必备
    【转】Servlet 生命周期、工作原理
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/2268904.html
Copyright © 2020-2023  润新知