首先,简单介绍下什么是线性表。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。线性表的逻辑结构简单,便于实现和操作。因此,线性表这种数据结构在实际应用中是广泛采用的一种数据结构。
然后让我们来看看线性表的两种实现方式基于数组(ArrayBaseList)和基于链表(LinkedList)。
一:基于数组的线性表实现
类List是虚基类,用于列出线性表要实现的相关功能,比如查找增删改查,该类会被AList(即基于数组的线性表)继承。其中由于线性表元素未知,故采用了模板参数。
/*
作为线性表的基类,规定了线性表要实现的一些方法
*/
#ifndef LIST_H
#define LIST_H
template<typename Elem>
class List{
public:
//清空线性表
virtual void clear() = 0;
//在fence位置插入元素(fence == position缩写)
virtual bool insert(const Elem&) = 0;
//在线性表尾增添元素
virtual bool append(const Elem&) = 0;
//移除特定元素
virtual bool remove(Elem&) = 0;
//把fence位置移到线性表最前面元素
virtual void setStart() = 0;
//把fence位置移到线性表末端,最后一个元素后面
virtual void setEnd() = 0;
//把fence位置向前移一位
virtual void prev() = 0;
//把fence位置向后移一位
virtual void next() = 0;
//获取fence位置左边元素个数
virtual int leftLength() const = 0;
//获取fence位置右边元素个数
virtual int rightLength() const = 0;
//设置fence的位置
virtual bool setFence(int fence) = 0;
//获取fence位置元素的值
virtual bool getValue(Elem&) const = 0;
//打印线性表
virtual void show() const = 0;
};
#endif
/*
*这是基于数组实现的线性表---ArrayBaseList
*/
#ifndef ALIST_H
#define ALIST_H
#include"List.h"
#include<iomanip>
#include<iostream>
using namespace std;
template<typename Elem>
class AList :public List<Elem>{
public:
AList(int ms){
maxSize = ms;
listSize = fence = 0;
listArray = new Elem[maxSize];
}
~AList(){
delete [] listArray;
}
//清空线性表
void clear(){
delete[] listArray;
listSize = fence = 0;
listArray = new Elem[maxSize];
}
//在fence位置插入
bool insert(const Elem & insertElem){
if (fence == maxSize)
return false;
else{
//先将要插入位置后面的所有元素向后移一位,腾出空间
for (int i = listSize; i > fence; --i)
listArray[i] = listArray[i - 1];
listArray[fence] = insertElem;
listSize++;
return true;
}
}
//在线性表末端插入
bool append(const Elem & appendElem){
if (listSize == maxSize)
return false;
else{
listArray[listSize++] = appendElem;
return true;
}
}
//移除fence位置的元素
bool remove(Elem & removeElem){
if (rightLength() == 0)
return false;
else{
removeElem = listArray[fence];
for (int i = fence; i < listSize - 1; ++i)
listArray[i] = listArray[i + 1];
listSize--;
return true;
}
}
//把fence设置在第一个元素之前
void setStart(){
fence = 0;
}
//把fence设置在最后一个元素后面
void setEnd(){
fence = listSize;
}
//把fence向前移一位
void prev(){
if (fence != 0)
fence--;
}
//把fence向后移一位
void next(){
if (fence != listSize)
fence++;
}
int leftLength() const{
return fence;
}
int rightLength() const{
return listSize - fence;
}
//设置fence的位置
bool setFence(int f){
if (f >= 0 && f <= listSize){
fence = f;
return true;
}
else
return false;
}
//获取fence位置元素的值
bool getValue(Elem& value) const{
if (fence == listSize)
return false;
else{
value = listArray[fence];
return true;
}
}
//打印线性表
void show() const{
for (int i = 0; i < listSize; i++){
cout << setw(7) << listArray[i];
if ((i + 1) % 10 == 0)
cout << endl;
}
cout << endl;
}
private:
//最大储存元素个数
int maxSize;
//实际储存元素
int listSize;
//标记位置,用于插入和删除
//若fence=1,则表明此刻插入和删除的位置在第一个元素的后面
int fence;
//用于储存元素的数组
Elem * listArray;
};
#endif
二:基于链表的线性表实现
为了后面实现更加简单,操作更加容易,我们采取增加一个头节点的方法来实现,头结点不存储数据,只指向第一个节点。下面一张图片简单介绍了头指针head,fence和tail的位置。注意fence是指向栅的前一个元素,你也可以考虑为什么不是后面(原因是插入和删除的复杂性将会增加)。
先简单地把节点的实现给大家,节点包含数据值和指针值,一个构造函数用于生成普通节点,另一个构造函数用于生成头结点。
template<typename Elem>
class Node{
public:
Elem value;
Node * next;
Node(const Elem & v, Node * n = NULL){
value = v;
next = n;
}
Node(Node * n = NULL){
next = n;
}
};
下面简单介绍下插入和删除操作,
上图是插入操作的示意图,插入步奏如下:
1:新建一个节点,把它的value值设置为要插入元素的值(节点实现等下给出)
2:把新建节点的next指针指向fence指向的next元素(此处是元素12)
3:把fence的next指向新建节点
上图是删除操作,操作步奏如下:
1: 先让一个it指针指向要删除的节点,保存it的value值
2:将fence指针指向it的next指向的节点
3:删除掉it节点
下面给出与基于链表线性表的具体实现
#include<iostream>
#include<iomanip>
#include"Node.h"
using namespace std;
template<typename Elem>
class LList :public Node<Elem>{
private:
//头结点
Node<Elem> * head;
//尾节点
Node<Elem> * tail;
//fence后面的第一个节点
Node<Elem> * fence;
//fence左边的元素个数
int leftCount;
//fence右边的元素个数
int rightCount;
//初始化
void init(){
head = tail = fence = new Node<Elem>();
leftCount = rightCount = 0;
}
//移除所有节点
void removeAll(){
while (head != NULL){
fence = head;
head = head->next;
delete fence;
}
}
public:
LList(){
init();
}
~LList(){
removeAll();
}
void clear(){
removeAll();
init();
}
void setStart(){
fence = head;
rightCount += leftCount;
leftCount = 0;
}
void setEnd(){
fence = tail;
leftCount += rightCount;
rightCount = 0;
}
void next(){
//当fence不是最后一个元素之后才有next
if (fence != tail){
fence = fence->next;
++leftCount;
--rightCount;
}
}
void prev(){
//当fence不是在第一个元素之前时才可以有prev
if (fence != head){
//从头开始遍历,查找fence的前一个元素
Node<Elem> * temp = head;
while (temp->next != fence)
temp = temp->next;
fence = temp;
--leftCount;
++rightCount;
}
}
int leftLength() const{
return leftCount;
}
int rightLength() const{
return rightCount;
}
//获取fence位置的元素值
bool getValue(Elem & v){
if (rightCount == 0)
returnf false;
else{
v = fence->next->value;
return true;
}
}
bool insert(const Elem & v){
fence->next = new Node<Elem>(v, fence->next);
if (tail == fence)
tail = fence->next;
rightCount++;
return true;
}
bool append(const Elem & v){
tail->next = new Node<Elem>(v, NULL);
tail = tail->next;
rightCount++;
return true;
}
bool remove(Elem & it){
if (fence == tail)
return false;
Node<Elem> * temp = fence->next;
it = temp->value;
fence->next = temp->next;
if (fence->next == NULL) //删掉最后一个时
tail = fence;
delete temp;
rightCount--;
return true;
}
bool setFence(const int pos){
if (pos < 0 || pos >(leftCount + rightCount))
return false;
else{
fence = head;
for (int i = 1; i <= pos; i++)
fence = fence->next;
rightCount = (leftCount + rightCount) - pos;
leftCount = pos;
return true;
}
}
void show() const{
Node<Elem> * temp = head;
temp = temp->next; //先把指针移到头结点的后一位,头结点不存储数据
int count = 0;
while (temp != NULL){
cout << setw(7) << temp->value;
temp = temp->next;
if (++count % 10 == 0)
cout << endl;
}
cout << endl;
}
};
总结:
两者的对比
1.时间开销
Array-Based Lists:
插入和删除的时间复杂度 (n).
Prev和直接访问的时间复杂度 (1).
数组的大小必须先被确定
当数组满时没有额外开销
Linked Lists:
插入和删除的时间复杂度 are (1).
Prev和直接访问的时间复杂性(n).
可以插入任意个元素,不用开始时设定最大元素个数
每个节点都需要额外开销
2.空间开销
D*E (Array-based List)
vs.
n*(P + E) (Linked List)
当 n>D*E/(P+E) 时,n(P + E) > DE;
E: 存储元素的大小
P: 指针的大小
D: 数组的大小
n: 实际元素的个数也就是链表的元素节点数
故当数组元素不是很满时,链表实现更有优势,当数组元素比较满时,数组的实现更有优势
这就是本人对线性表的理解,如果有不正确或者不深刻之处,还望大家指出。