启迪思维:链式链表

一:线性表的简单回顾

上一篇写了顺序存储,通过实验,可以比较清楚的看到,在头部插入需要移动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、鼓励自己能坚持把更多数据结构方面的知识写出来,让自己掌握更深刻,也顺便冒充下"小牛";

 

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

原文地址:https://www.cnblogs.com/sunysen/p/2974046.html