• 启迪思维:链式链表


    一:线性表的简单回顾

    上一篇写了顺序存储,通过实验,可以比较清楚的看到,在头部插入需要移动n次,网上很多往往以此来判断顺序存储效率低(当然我们可以通过代码控制每次添加元素都加入链表的尾部),其实一种数据结构两种实现方法,效率高低主要取决内存模型。

    二:链表名词解释

    1、链表的每个节点都包含一个数据域指针域

    2数据域中包含当前的数据

    3指针域中包含下一个节点的指针

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

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

    如下示例图

     

    代码段如下 

     1 /*
     2  * LNode.h
     3  *
     4  *  Created on: Mar 18, 2013
     5  *      Author: sunysen
     6  */
     7 
     8 #ifndef LNODE_H_
     9 #define LNODE_H_
    10 
    11 template<class T> struct LNode{
    12 public:
    13     T data;//数据域
    14     LNode<T> *next;//指针域
    15     LNode(T value):data(value),next(0){}
    16 
    17 };
    18 
    19 #endif /* LNODE_H_ */

    三:链表的优缺点

    链表最大的一个缺点,就是查找一个元素,必须整个链表遍历,也可以看出这种数据结构便于插入或者删除一个接点;

    四:应用范围

    链表用来构建许多其它数据结构,如堆栈,队列和他们的派生;

    五:代码分析

    1、插入节点

    在头结点或者未节点插入一个新的节点比较简单、在一个有前后驱节点位置插入比较困难(1、需要找到相应的位置;2、修改前驱节点指针域值和新加入节点指针域)。如下图

    代码段如下:

     1 bool Insert(const int i,const T e){
     2 
     3         int j = 0;
     4         LNode<T> *p = head.get();
     5 
     6         //找到要插入节点位置前节点
     7         while(p != 0 && j < i -1){
     8             ++j;
     9             p = p->next;
    10         }
    11         if(p == 0 || j > i-1){
    12             return false;
    13         }
    14 
    15         //新创建一个节点,当频繁插入和删除数据,
    16         //下面这样内存操作方式是非常低效的(频繁分配内存和释放内存已经非常低效,
    17         //而且会造成内存碎片),大多算情况list里边存储都小对象,这样应用场景适合
    18         //在系统加载时候分配很多小内存装入内存池,需要直接从内存池拿,释放时放回内存池
    19         //更多关于stl内存分配问题请参考侯捷<<STL源码剖析>>
    20         LNode<T> *q = new LNode<T>(e);
    21         if(q == 0){
    22             return false;
    23         }
    24         //更新新创建的指针域
    25         q->next = p->next;
    26         //前节点指针域指向新创建节点
    27         p->next = q;
    28 
    29         return true;
    30     }

    2、删除节点

    每次想到自己能搞懂数据结构一部分知识,都在心里默默感谢发明图的人(推荐大家看看<<打开餐巾纸>>),如下图

     代码段如下:

     1  /**
     2      *删除指定位置链表元素
     3      */
     4     bool Delete(const int i) const{
     5         int j = 0;
     6         LNode<T> *p = head.get();
     7 
     8         //找到要删除的节点
     9         while(p->next != 0 && j < i -1){
    10             j++;
    11             p = p->next;
    12         }
    13 
    14         if(p->next == 0 || j > i){
    15             return false;
    16         }
    17 
    18         //释放删除节点占用内存
    19         std::auto_ptr<LNode<T> > new_ptr(p->next);
    20         //更新删除节点前一个节点指针域
    21         p->next = new_ptr->next;
    22         return true;
    23     }

    3、清空链表代码段如下:

     1 /**
     2      *清空链表数据并且释放内存
     3      */
     4     void Clear(){
     5         LNode<T> *p = head->next;
     6 
     7         while(p != 0){
     8             //更多关于auto_ptr知识,请阅读memory里auto_ptr源码,
     9             std::auto_ptr<LNode<T> > new_ptr(p);
    10             p = new_ptr->next;
    11         }
    12 
    13         head->next = 0;
    14 
    15     }

    4、判断是否为空代码段如下:

    1 /**
    2      * 判断链表是否为空
    3      */
    4     bool Empty() const{
    5         return head->next == 0;
    6     }

     5、反转链表代码段如下:

     1 /**
     2      *链表反转,网上看到很多人面试的时候都让写这个,写一个练练手,以防万一
     3      *基本思路,就是用两个节点指针,保证一个节点指针是另个节点指针前驱节点
     4      *最简单方法,用一个只有两个节点链表,反推效果
     5     */
     6     void Invert(){
     7         LNode<T> *head_ptr = head.get();
     8         //p是head_ptr前驱节点,q和r指针都是中间变量
     9         LNode<T> *p  = head_ptr,*q = head_ptr,*r=0;
    10         head_ptr =  head_ptr->next;
    11 
    12         while(head_ptr){
    13             r = head_ptr->next;
    14             head_ptr->next = p;
    15             p = head_ptr;
    16             head_ptr = r;
    17         }
    18         head_ptr = p;
    19         q->next = 0;
    20 
    21         while(p != 0){
    22             std::cout<<"p is value = "<<p->data<<"\n";
    23             p = p->next;
    24         }
    25     }

    6、计算链表长度代码段如下:

     1 /**
     2      * 计算链表元素的长度
     3      */
     4     int Length() const{
     5         int i = 0;
     6         LNode<T> *p = head.get()->next;
     7         //操作是O(N),相对来说效率已经很高
     8         while(p != 0){
     9             p = p->next;
    10             ++i;
    11         }
    12         return i;
    13     }

    7、测试代码段如下: 

     1 /**
     2  * 测试链表各个方法
     3  */
     4 void test(){
     5     std::cout<<"-----------insert begin------------"<<std::endl;
     6     for(int i = 1; i < 5; i++){
     7         Insert(1,i);
     8     }
     9     std::cout<<"-----------insert end------------"<<std::endl;
    10 
    11     std::cout<<"-----------calculated length begin----------"<<std::endl;
    12     std::cout<<"frist list length="<<Length()<<std::endl;
    13     std::cout<<"-----------calculated length end----------"<<std::endl;
    14 
    15     std::cout<<"-----------delete begin----------"<<std::endl;
    16     Delete(1);
    17     std::cout<<"-----------delete end----------"<<std::endl;
    18 
    19     std::cout<<"-----------calculated length begin----------"<<std::endl;
    20     std::cout<<"second list length="<<Length()<<std::endl;
    21     std::cout<<"-----------calculated length end----------"<<std::endl;
    22 
    23     std::cout<<"-----------traversal of the elemetns  begin----------"<<std::endl;
    24     LNode<int> *p = head->next;
    25 
    26     while(p != 0){
    27         if(p->data){
    28             std::cout<<"p is value = "<<p->data<<std::endl;
    29         }
    30         p = p->next;
    31     }
    32 
    33     std::cout<<"-----------traversal of the elemetns  end----------"<<std::endl;
    34 
    35     std::cout<<"--------------------list begin------------------"<<std::endl;
    36     Clear();
    37     std::cout<<"--------------------list end------------------"<<std::endl;
    38 
    39     std::cout<<"----------------------------------------------------"<<std::endl;
    40 
    41     std::cout<<"-----------calculated length begin----------"<<std::endl;
    42     std::cout<<"third list length="<<Length()<<std::endl;
    43     std::cout<<"-----------calculated length end----------"<<std::endl;
    44 
    45     std::cout<<"------------万恶的分割线-------------------"<<std::endl;
    46     for(int i = 1; i < 5; i++){
    47         Insert(1,i);
    48     }
    49     std::cout<<"------------insert end--------------------------"<<std::endl;
    50 
    51     std::cout<<"--------------calculated length begin-----------------"<<std::endl;
    52     std::cout<<"fourth list length="<<Length()<<std::endl;
    53     std::cout<<"--------------calculated length end-----------------"<<std::endl;
    54 
    55     std::cout<<"-----------traversal of the elemetns  begin----------"<<std::endl;
    56     LNode<int> *p1 = head->next;
    57 
    58     while(p1 != 0){
    59         if(p1->data){
    60             std::cout<<"p is value = "<<p1->data<<std::endl;
    61         }
    62         p1 = p1->next;
    63     }
    64     std::cout<<"-----------traversal of the elemetns  end----------"<<std::endl;
    65 
    66     std::cout<<"-----------invert of the elemetns  begin----------"<<std::endl;
    67     Invert();
    68     std::cout<<"-----------invert of the elemetns  end----------"<<std::endl;
    69 }

    8、运行结果

    链表运行结果

     9、完整代码

    View Code
      1 #include "core/node/LNode.h"
      2 //#include "memory.h"
      3 //#include <iostream>
      4 
      5 /**
      6  * 下面类用到模板相关知识(更多模板知识请参考Effective C++第七章或者C++_Templates完全导引)
      7  * 正常运行下面的方法还需要两个头文件(#include "memory.h" #include <iostream>),
      8  * 一般项目开发中头文件都在一个公共文件中(更多请参考参考Effective C++里边条款31)
      9  */
     10 template <class T>
     11 class LinkList {
     12 private:
     13     //获取资源立即放入管理对象(参考Effective C++里边条款13)
     14     //tr1::shared_ptr(引用计数智慧指针)管理对象比auto_ptr更强大
     15     std::auto_ptr<LNode<T> > head;
     16 public:
     17     LinkList():head(new LNode<T>(0)){
     18         head->next = 0;
     19     }
     20     /**
     21      * 析构函数释放资源
     22      */
     23     ~LinkList(){
     24         Clear();
     25     }
     26 
     27     /**
     28      *清空链表数据并且释放内存
     29      */
     30     void Clear(){
     31         LNode<T> *p = head->next;
     32 
     33         while(p != 0){
     34             //更多关于auto_ptr知识,请阅读memory里auto_ptr源码,
     35             std::auto_ptr<LNode<T> > new_ptr(p);
     36             p = new_ptr->next;
     37         }
     38 
     39         head->next = 0;
     40 
     41     }
     42     /**
     43      * 判断链表是否为空
     44      */
     45     bool Empty() const{
     46         return head->next == 0;
     47     }
     48 
     49     /**
     50      *删除指定位置链表元素
     51      */
     52     bool Delete(const int i) const{
     53         int j = 0;
     54         LNode<T> *p = head.get();
     55 
     56         //找到要删除的节点
     57         while(p->next != 0 && j < i -1){
     58             j++;
     59             p = p->next;
     60         }
     61 
     62         if(p->next == 0 || j > i){
     63             return false;
     64         }
     65 
     66         //释放删除节点占用内存
     67         std::auto_ptr<LNode<T> > new_ptr(p->next);
     68         //更新删除节点前一个节点指针域
     69         p->next = new_ptr->next;
     70         return true;
     71     }
     72 
     73     bool Insert(const int i,const T e){
     74 
     75         int j = 0;
     76         LNode<T> *p = head.get();
     77 
     78         //找到要插入节点位置前节点
     79         while(p != 0 && j < i -1){
     80             ++j;
     81             p = p->next;
     82         }
     83         if(p == 0 || j > i-1){
     84             return false;
     85         }
     86 
     87         //新创建一个节点,当频繁插入和删除数据,
     88         //下面这样内存操作方式是非常低效的(频繁分配内存和释放内存已经非常低效,
     89         //而且会造成内存碎片),大多算情况list里边存储都小对象,这样应用场景适合
     90         //在系统加载时候分配很多小内存装入内存池,需要直接从内存池拿,释放时放回内存池
     91         //更多关于stl内存分配问题请参考侯捷<<STL源码剖析>>
     92         LNode<T> *q = new LNode<T>(e);
     93         if(q == 0){
     94             return false;
     95         }
     96         //更新新创建的指针域
     97         q->next = p->next;
     98         //前节点指针域指向新创建节点
     99         p->next = q;
    100 
    101         return true;
    102     }
    103 
    104 
    105     /**
    106      * 计算链表元素的长度
    107      */
    108     int Length() const{
    109         int i = 0;
    110         LNode<T> *p = head.get()->next;
    111         //操作是O(N),相对来说效率已经很高
    112         while(p != 0){
    113             p = p->next;
    114             ++i;
    115         }
    116         return i;
    117     }
    118     /**
    119      *链表反转,网上看到很多人面试的时候都让写这个,写一个练练手,以防万一
    120      *基本思路,就是用两个节点指针,保证一个节点指针是另个节点指针前驱节点
    121      *最简单方法,用一个只有两个节点链表,反推效果
    122      */
    123     void Invert(){
    124 
    125         LNode<T> *head_ptr = head.get();
    126         //p是head_ptr前驱节点,q和r指针都是中间变量
    127         LNode<T> *p  = head_ptr,*q = head_ptr,*r=0;
    128         head_ptr =  head_ptr->next;
    129 
    130         while(head_ptr){
    131             r = head_ptr->next;
    132             head_ptr->next = p;
    133             p = head_ptr;
    134             head_ptr = r;
    135             std::cout<<"invert is value="<<p->data<<std::endl;
    136         }
    137         head_ptr = p;
    138         q->next = 0;
    139 
    140     }
    141     /**
    142      * 测试链表各个方法
    143      */
    144     void test(){
    145         std::cout<<"-----------insert begin------------"<<std::endl;
    146         for(int i = 1; i < 5; i++){
    147             Insert(1,i);
    148         }
    149         std::cout<<"-----------insert end------------"<<std::endl;
    150 
    151         std::cout<<"-----------calculated length begin----------"<<std::endl;
    152         std::cout<<"frist list length="<<Length()<<std::endl;
    153         std::cout<<"-----------calculated length end----------"<<std::endl;
    154 
    155         std::cout<<"-----------delete begin----------"<<std::endl;
    156         Delete(1);
    157         std::cout<<"-----------delete end----------"<<std::endl;
    158 
    159         std::cout<<"-----------calculated length begin----------"<<std::endl;
    160         std::cout<<"second list length="<<Length()<<std::endl;
    161         std::cout<<"-----------calculated length end----------"<<std::endl;
    162 
    163         std::cout<<"-----------traversal of the elemetns  begin----------"<<std::endl;
    164         LNode<int> *p = head->next;
    165 
    166         while(p != 0){
    167             if(p->data){
    168                 std::cout<<"p is value = "<<p->data<<std::endl;
    169             }
    170             p = p->next;
    171         }
    172 
    173         std::cout<<"-----------traversal of the elemetns  end----------"<<std::endl;
    174 
    175         std::cout<<"--------------------list begin------------------"<<std::endl;
    176         Clear();
    177         std::cout<<"--------------------list end------------------"<<std::endl;
    178 
    179         std::cout<<"----------------------------------------------------"<<std::endl;
    180 
    181         std::cout<<"-----------calculated length begin----------"<<std::endl;
    182         std::cout<<"third list length="<<Length()<<std::endl;
    183         std::cout<<"-----------calculated length end----------"<<std::endl;
    184 
    185         std::cout<<"------------万恶的分割线-------------------"<<std::endl;
    186         for(int i = 1; i < 5; i++){
    187             Insert(1,i);
    188         }
    189         std::cout<<"------------insert end--------------------------"<<std::endl;
    190 
    191         std::cout<<"--------------calculated length begin-----------------"<<std::endl;
    192         std::cout<<"fourth list length="<<Length()<<std::endl;
    193         std::cout<<"--------------calculated length end-----------------"<<std::endl;
    194 
    195         std::cout<<"-----------traversal of the elemetns  begin----------"<<std::endl;
    196         LNode<int> *p1 = head->next;
    197 
    198         while(p1 != 0){
    199             if(p1->data){
    200                 std::cout<<"p is value = "<<p1->data<<std::endl;
    201             }
    202             p1 = p1->next;
    203         }
    204         std::cout<<"-----------traversal of the elemetns  end----------"<<std::endl;
    205 
    206         std::cout<<"-----------invert of the elemetns  begin----------"<<std::endl;
    207         Invert();
    208         std::cout<<"-----------invert of the elemetns  end----------"<<std::endl;
    209     }
    210 };

    单向链表在查询很多操作时间复杂度为O(N),这在对查询要求比较高情况,就不太合适,所以就有了很多的优化方案,比如:双向链表,循环链表(都是以空间换取时间)等等,后面会给出各自实现代码,和大家一起学习。

    六:环境

    1、运行环境:Ubuntu 10.04 LTS+VMware8.0.4+gcc4.4.3;

    2、开发工具:Eclipse+make

    七:题记

    1、上面的代码难免有bug,如果你发现代码写的有问题,请你帮忙指出,让我们一起进步,让代码变的更漂亮和更健壮;

    2、我自己能手动写上面代码,离不开郝斌、高一凡、侯捷、严蔚敏等老师的书籍和视频指导,在这里感谢他们;

    3、鼓励自己能坚持把更多数据结构方面的知识写出来,让自己掌握更深刻,也顺便冒充下"小牛";

     

    欢迎继续阅读“启迪思维:数据结构和算法”系列

  • 相关阅读:
    Cookie的总结
    动态改变静态资源路径
    使用JS监听DOM元素的属性及动画、CSS过渡
    localStorage和sessionStorage使用及监听
    难理解的点---值方法和指针方法 + 接口赋值
    js关于精确判断数据类型的总结
    ivew版本4.5.0后ivu-row样式变更,导致布局错乱
    简述三种异步上传文件方式
    自然周算法-javascript实现
    时隔3年9个月,再看
  • 原文地址:https://www.cnblogs.com/sunysen/p/2974046.html
Copyright © 2020-2023  润新知