前言:
对于顺序存储结构线性表的最大问题就是插入和删除需要移动大量的元素,而这使代码效率变得低下,所以这里就要引入我们的链式存储结构,链式存储结构为了表示每个数据元素与其直接后继元素之间的逻辑关系,数据元素除了存储本身的信息外,还需要存储其后继的信息,如下图表示,ai元素的数据结构保存了下一个ai+1元素的地址,就像小朋友过马路,一个牵着一个形成链式结构,链表中的数据元素在物理内存中无相邻关系
链式存储逻辑结构:
链表包括单链表,循环链表,双向链表
单链表:每个结点只包含直接后继的地址信息
循环链表:单链表的最后一个结点的直接后继为第一个结点(绕成一个圈)
双向链表:单链表中的结点包含直接前驱和后继的地址信息
链表的操作
单链表中的结点定义:
struct Node:public Object//struct 和 class用法一致,只是struct默认是公有
{
T value;//数据元素的具体类型
Node* next //指向后继结点的指针
}
插入元素操作:
1.从头结点开始,通过current指针定位到目标位置(比如在a结点和c结点(ac相连)直接插入b节点,可以用将current指针移动到a)
2.在堆空间申请新的b结点,赋值数据域
3.把a结点的next结点赋给b结点的next,b结点地址赋给a结点的next
删除元素操作
1.从头结点开始,通过current指针定位到目标位置(比如要删除b结点,(abc相连),可以用将current指针移动到a)
2.使用toDel指针指向需要删除的结点b
3.将a结点的next赋给toDel即b,将toDel的next即c赋给current指针即a的next,删除toDel,即删除b结点
linkList类模板设计要点
继承于list类,通过头结点访问后继结点
定义内部结点类型Node,用于描述数据域和指针域
实现线性表的关键操作(增删改查)
代码表示:
template <typename T>
class LinKList: public List<T>
{
protected:
struct Node : public Object
{
T value;
Node *next;
}
mutable Node m_header;
int m_length;
public:
LinkList();
}
代码优化1
但是我们上面的代码还是存在一定的弊端,比如我们放入结点的是一个类对象,那必然调用LinkListlist先创建一个头结点,但是要是我们的Test类的构造函数抛出异常呢,这时候创建链表就会不成功了!,比如以下代码肯定打印不出
class Test
{
public:
Test()
{
throw 0;
}
};
int main()
{
LinkList <Test> list;
cout<<"happy"<<endl;
return 0;
}
所以上面的代码表示应该改为以下方案,但是在内存布局上是一致的
template <typename T>
class LinkList: public List<T>
{
protected:
struct Node:Object
{
T value;
Node *next;
};
//mutable Node m_header;
mutable struct{
char reserved[sizeof(T)];
Node* next;
}m_header;
int m_length;
public:
LinkList()
{
};
代码优化2
我们的insert,remove,get,set等操作都涉及元素定位,所以我们可以把这部分抽象出来,
Node* position(int i)const
{
Node* ret = reinterpret_cast<Node*>(&m_header);
for(int p=0;p<i;p++)
{
ret = ret->next;
}
return ret;
}
链式线性表类的代码:
#ifndef LINKLIST_H
#define LINKLIST_H
#include "list.h"
#include "Exception.h"
#include <iostream>
using namespace std;
namespace CGSLib
{
template <typename T>
class LinkList: public List<T>
{
protected:
struct Node:public Object
{
T value;
Node *next;
};
//mutable Node m_header;
mutable struct:public Object{
char reserved[sizeof(T)];
Node* next;
}m_header;
int m_length;
Node* position(int i)const
{
Node* ret = reinterpret_cast<Node*>(&m_header);
for(int p=0;p<i;p++)
{
ret = ret->next;
}
return ret;
}
public:
LinkList()
{
m_header.next = NULL;
m_length = 0;
};
bool instert(const T&e)
{
return instert(m_length,e);
}
bool instert(int i,const T& e)
{
bool ret = ((0<=i)&&(i<=m_length));
if(ret)
{
Node *node = new Node();
if(node!=NULL)
{
/*
Node *current = &m_header;
for(int p = 0;p<i;p++)
{
current = current->next;
}
*/
Node* current = position(i);
node->value = e;
node->next = current->next;
current->next = node;
m_length++;
}
}
else
{
THROW_EXCEPTION(NoEnougMemoryException,"no memory to insert now");
}
return ret;
}
bool remove(int i)
{
bool ret = ((0<=i)&&(i<m_length));
if(ret)
{
/*
Node* current = &m_header;
for(int p = 0;p<i;p++)
{
current = current->next;
}
*/
Node* current = position(i);
Node* toDel = current->next;
current->next = toDel->next;
delete toDel;
m_length--;
}
return ret;
}
bool set(int i,const T& e)
{
bool ret = ((0<=i)&&(i<m_length));
if(ret)
{
/*
Node* current = &m_header;
for(int p = 0;p<i;p++)
{
current = current->next;
}
*/
Node* current = position(i);
current->next->value = e;
}
return ret;
}
T get(int i)const
{
T ret;
if(get(i,ret))
{
return ret;
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException,"Invalid paramter i to get element");
}
return ret;
}
bool get(int i,T& e)const
{
bool ret = ((0<=i)&&(i<m_length));
if(ret)
{
/*
Node* current = &m_header;
for(int p = 0;p<i;p++)
{
current = current->next;
}
*/
Node* current = position(i);
e = current->next->value;
}
return ret;
}
int length()const
{
return m_length;
}
void clear()
{
while(m_header.next)
{
Node* toDel = m_header.next;
m_header.next = toDel->next;
delete toDel;
}
m_length = 0;
}
~LinkList()
{
clear();
}
};
}
#endif
单链表的元素查看
在线性表中我们要查找某一个元素可以遍历该线性表,如果找到我们要的元素则返回,否则返回错误,在这里我们用一个find函数来处理,代码如下
顺序表中的find
int find(const T& e)const
{
int ret = -1;
int i;
for(i=0;i< m_length;i++)
{
if(m_array[i] == e)
{
ret = i;
break;
}
}
return ret;
}
单链表中的find
int find(const T& e)const
{
int ret = -1;
int i = 0;
Node * node = m_header.next;
while(node)
{
if(node->value == e)
{
ret = i;
break;
}
else
{
node = node->next;
i++;
}
}
return ret;
}
main.c
#include <iostream>
#include "LinkList.h"
using namespace std;
using namespace CGSLib;
class Test
{
int i;
public:
Test(int v = 0)
{
i = v;
}
bool operator ==(const Test& obj)
{
return true;
}
};
int main()
{
Test t1(1);
Test t2(1);
Test t3(3);
LinkList<Test> list;
/*
for(int i =0;i<5;i++)
{
list.instert(0,i);
}
cout<<list.find(- 3)<<endl;
*/
return 0;
}
对于上面的find函数功能是可以测试成功的,但是当我们的对象是某个类的时候我们就必须要在这个类中重载 == 操作符函数,因为在find函数中不能直接将对象进行比较,这样每个类都需要去重载一个这样一个操作符函数,所以我们只需要在顶层父类object中加入该重载操作符函数,所以上述代码可以修改为如下代码,可以看出我们的顶层父类Object的重载操作符方法只是为了编译通过,具体还要根据不同类的比较方式具体重载父类的函数
#include <iostream>
#include "LinkList.h"
#include "object.h"
using namespace std;
using namespace CGSLib;
class Test : public Object
{
int i;
public:
Test(int v = 0)
{
i = v;
}
bool operator == (const Test& t)
{
return (i == t.i);
}
};
int main()
{
Test t1(1);
Test t2(1);
Test t3(3);
LinkList<Test> list;
list.instert(t1);
list.instert(t2);
list.instert(t3);
cout<<list.find(t2)<<endl;
/*
for(int i =0;i<5;i++)
{
list.instert(0,i);
}
cout<<list.find(- 3)<<endl;
*/
return 0;
}
顺序表和单链表的对比分析
顺序表的整体时间复杂度比单链表要低,那么单链表还有使用价值吗?在实际工作开发中,时间复杂度只是效率的一个参考指标,对于内置基础类型,比如int,char这些基础类型,顺序表和单链表是不相上下的,但是自定义类类型来说,顺序表在效率上低于单链表
对于插入和删除
顺序表中涉及大量数据对象的复制操作,在顺序表中插入一个元素,就需要将目标位置的元素往后移动,单链表只涉及指针操作,效率与数据对象无关,而且对于指针类型和基础类型(int)来说一般都是4字节,所以在插入和删除中效率不相上下,但是对于自定义类型来说,该自定义类型对象是远远不止4个字节,所以对于自定义类型来说顺序表在效率上低于单链表,总的来说,大数据对象存储时就使用单链表
对于数据访问
对于顺序表来说是随机访问,可直接定位数据对象,但是对于单链表来说是顺序访问的,必须从头访问数据对象,无法直接定位,所以对于数据访问来说顺序表会更加优秀
小结:
顺序表适用于访问需求量比较大的场合(随机访问),单链表适用于数据元素频繁插入删除的场合(顺序访问),当数据类型相对简单时,顺序表和单链表效率不相上下,当数据元素复杂的时候单链表效率远高于顺序表
单链表的遍历
对于单链表的遍历其实一个for循环就能够搞定,比如以下代码
#include <iostream>
#include "LinkList.h"
#include "object.h"
using namespace std;
using namespace CGSLib;
int main()
{
LinkList<int> list;
for(int i=0;i<5;i++)
{
list.instert(0,i);
}
for(int i=0;i<5;i++)
{
cout<<list.get(i)<<endl;
}
return 0;
}
结果为:
4
3
2
1
0
但是在上述代码存在一个问题,就是instert函数的时间复杂度是O(n),而get函数的时间复杂度是O(nn),所以上述代码不能以线性的时间复杂度完成单链表的遍历,所以我们得为单链表提供新的方法,在线性时间内完成遍历
设计思路
在单链表的内部定义一个游标(Node m_current)
遍历开始前将游标指向位置为0的数据元素
获取游标指向的数据元素
通过结点中的next指针移动游标
我们用自定义move()函数将游标定位到目标位置,用自定义next()函数移动游标,用自定义函数current()获取游标所指向的数据元素,用自定义函数end()判断游标是否到达尾部,用step指示每次移动的数目
代码如下:
LinkList.h
#ifndef LINKLIST_H
#define LINKLIST_H
#include "list.h"
#include "Exception.h"
#include <iostream>
using namespace std;
namespace CGSLib
{
template <typename T>
class LinkList: public List<T>
{
protected:
struct Node:public Object
{
T value;
Node *next;
};
//mutable Node m_header;
mutable struct:public Object{
char reserved[sizeof(T)];
Node* next;
}m_header;
int m_length;
Node *m_current;
int m_step;
Node* position(int i)const
{
Node* ret = reinterpret_cast<Node*>(&m_header);
for(int p=0;p<i;p++)
{
ret = ret->next;
}
return ret;
}
public:
LinkList()
{
m_header.next = NULL;
m_length = 0;
m_current = NULL;
m_step = 1;
};
bool move(int i,int step = 1)
{
bool ret = (0<=i)&&(i<m_length)&&(step>0);
if(ret)
{
m_current = position(i)->next;
m_step = step;
}
return ret;
}
bool end()
{
return (m_current == NULL);
}
T current()
{
if(!end())
{
return m_current->value;
}
else
{
THROW_EXCEPTION(InvalidParamterException,"No value at current position...");
}
}
bool next()
{
int i = 0;
while((i<m_step)&&!end())
{
m_current = m_current->next;
i++;
}
return (i==m_step);
}
int find(const T& e)const
{
int ret = -1;
int i = 0;
Node * node = m_header.next;
while(node)
{
if(node->value == e)
{
ret = i;
break;
}
else
{
node = node->next;
i++;
}
}
return ret;
}
bool instert(const T&e)
{
return instert(m_length,e);
}
bool instert(int i,const T& e)
{
bool ret = ((0<=i)&&(i<=m_length));
if(ret)
{
Node *node = new Node();
if(node!=NULL)
{
/*
Node *current = &m_header;
for(int p = 0;p<i;p++)
{
current = current->next;
}
*/
Node* current = position(i);
node->value = e;
node->next = current->next;
current->next = node;
m_length++;
}
}
else
{
THROW_EXCEPTION(NoEnougMemoryException,"no memory to insert now");
}
return ret;
}
bool remove(int i)
{
bool ret = ((0<=i)&&(i<m_length));
if(ret)
{
/*
Node* current = &m_header;
for(int p = 0;p<i;p++)
{
current = current->next;
}
*/
Node* current = position(i);
Node* toDel = current->next;
current->next = toDel->next;
delete toDel;
m_length--;
}
return ret;
}
bool set(int i,const T& e)
{
bool ret = ((0<=i)&&(i<m_length));
if(ret)
{
/*
Node* current = &m_header;
for(int p = 0;p<i;p++)
{
current = current->next;
}
*/
Node* current = position(i);
current->next->value = e;
}
return ret;
}
T get(int i)const
{
T ret;
if(get(i,ret))
{
return ret;
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException,"Invalid paramter i to get element");
}
return ret;
}
bool get(int i,T& e)const
{
bool ret = ((0<=i)&&(i<m_length));
if(ret)
{
/*
Node* current = &m_header;
for(int p = 0;p<i;p++)
{
current = current->next;
}
*/
Node* current = position(i);
e = current->next->value;
}
return ret;
}
int length()const
{
return m_length;
}
void clear()
{
while(m_header.next)
{
Node* toDel = m_header.next;
m_header.next = toDel->next;
delete toDel;
}
m_length = 0;
}
~LinkList()
{
clear();
}
};
}
#endif // LINKLIST_H
main.c
#include <iostream>
#include "LinkList.h"
#include "object.h"
using namespace std;
using namespace CGSLib;
int main()
{
LinkList<int> list;
for(int i=0;i<5;i++)
{
list.instert(0,i);
}
for(list.move(0,1);!list.end();list.next())
{
cout<<list.current()<<endl;
}
return 0;
}
可以看出上述代码遍历链表的时间复杂度变为O(n),不用每次从头遍历,只需要一个游标定位到要开始遍历的位置(list.move()),然后打印出该位置的value值(list.current()),接着调用list.next()移动游标,直到游标不为空为止