这个周末,复习了线性表的常用存储方式。以前总是看得多,写的少,这次真的写起来,发现自己手生的很,都是眼高手低的结果。在写程序的过程中,也遇到了一些问题和以前没有注意的地方。这次深深的体会到了:自己手写一遍比看书5遍都收获大。所以,借此奉劝和我一样眼高手低的园友:实践,实践,再实践……
下面介绍常用的四种存储方式,附上源码:
顺序表:这是大家最为熟悉的一种描述,相对比较简单。
优点:存储空间小,不需要额外增加空间,元素按位置查找方便,速度快。
缺点:
必须预先分配空间,在删除和插入操作时,需要移动元素,因此时间复杂度与元素大小成线性关系,面对大型类对象时,此缺点是致命的。
源码:

Code

/**//********************************************************************
created: 2008/05/24
created: 24:5:2008 21:18
filename: MyArrayList.h
file path:
file base: MyArrayList
file ext: h
author: CY
purpose: 线性表的顺序存储
*********************************************************************/

#ifndef _MYARRAYLIST_H_
#define _MYARRAYLIST_H_

#include <iostream.h>
#include <cstdlib>
#include <exception>

template<class T>
class MyArrayList


{
public:
MyArrayList(int MaxSpace = 100) //容量初始化为MaxSpace

{
m_size = MaxSpace;
element = new T[m_size];
m_length = 0;
}
~MyArrayList()

{
delete []element;
}

bool IsEmpty() const //判断链表空

{
return m_length == 0;
}

int Length() const //求当前元素个数

{
return m_length;
}

bool FindK(int k, T& x) const; //将第k个元素返回给x

int Search(const T& x) const; //返回元素x所在位置

MyArrayList<T>& DeleteK(int k, T& x); //删除第k个元素

MyArrayList<T>& InsertK(int k, const T& x); //在第k个元素后插入元素x

friend ostream& operator <<(ostream& out,const MyArrayList<T>& mylist);
private:
int m_size; //定义数组容量
int m_length; //当前元素个数
T *element; //动态数组
};


template<class T>
bool MyArrayList<T>::FindK(int k, T& x) const


{
if (k < 1 || k > m_length)

{
return false;
}
x = element[k-1];
return true;
}

template<class T>
int MyArrayList<T>::Search(const T& x) const


{
int i;
for (i=1; i < m_length+1; i++)

{
if ( x == element[i-1])

{
break;
}
}
return (i==m_length+1? -1:i);
}

template<class T>
MyArrayList<T>& MyArrayList<T>::DeleteK(int k, T& x)


{
if (FindK(k,x)) //删除位置合法

{
for (int i=k; i < m_length; i++)

{
element[i-1] = element[i];//删除位置后面的元素前移一位
}
m_length--; //长度减一
return *this;
}
else throw exception(); //删除位置不合法
}

template<class T>
MyArrayList<T>& MyArrayList<T>::InsertK(int k,const T& x)


{
if ( m_length == m_size || k<1 || k>m_length+1) //插入位置不合法

{
throw exception();
}
m_length++;
for (int i=m_length; i>=k; i--)

{
element[i] = element[i-1];
}
element[k-1] = x;
return *this;
}

template<class T>
ostream& operator <<(ostream& out,const MyArrayList<T>& mylist)


{
for (int i=0; i<mylist.m_length; i++)

{
out << mylist.element[i]<<" ";
}
out << endl;
return out;
}
#endif

链表:
优点:删除和插入操作不需要移动元素,表长可动态改变。
缺点:按位置查找需要遍历链表,存储需要额外的空间。
源码:

Code

/**//********************************************************************
created: 2008/05/23
created: 24:5:2008 9:48
filename: MyNodeList.h
file path:
file base: MyNodeList
file ext: h
author: CY
purpose: 线性表的链式存储结构
*********************************************************************/
#ifndef _MYNODELIST_H_
#define _MYNODELIST_H_
#include <iostream.h>
#include <cstdlib>
#include <exception>

template<class T>
struct Node //定义结点


{
T m_data;
Node<T>* m_link;
};


template<class T>
class MyNodeList


{
friend ostream& operator<<(ostream& out, const MyNodeList<T>& mylist);//重载运算符
public:
MyNodeList()

{
Head = 0; //置头结点为空
}

~MyNodeList();

bool IsEmpty() const //判断链表空

{
return Head==0;
}

int Length() const; //求链表长度

bool FindK(int k, T &x);//将第k个节点内容返回到x

int Search(const T &x); //搜索x所在位置

MyNodeList<T>& DeleteK(int k, T &x); //删除第k个节点,并将其数据返回给x

MyNodeList<T>& InsertK(int k, const T &x);//在第k个节点后插入x

private:
Node<T>* Head;
};


template<class T>
int MyNodeList<T>::Length() const


{
Node<T> *p=Head;
int i=0;
while (p != 0)

{
i++;
p = p->m_link;
}
return i;
}

template<class T>
bool MyNodeList<T>::FindK(int k, T &x)


{
Node<T> *p=Head;
int i=0;

if ( k<1)//位置不正确

{
throw exception();
}
while( p && i != k)

{
i++;
p=p->m_link;
}
if ( i == k)//找到第k个节点

{
x = p->m_data;
return true;
}
return false;
}

template<class T>
int MyNodeList<T>::Search(const T &x)


{
Node<T> *p=Head;
int i=0;
while(p && p->m_data!=x)

{
i++;
p=p->m_link;
}
if (p->m_data == x)

{
return i;
}
return -1;
}

template<class T>
MyNodeList<T>& MyNodeList<T>::DeleteK(int k, T &x)


{
Node<T>* p=Head;
int i=0;

if (k<1 || p==0)

{
throw exception();
}

if (k==1)

{
}
else

{
for (i=1; (p!=0)&&(i!=k); p=p->m_link,i++)

{
}
if (i!=k || p->m_link==0)

{
throw exception();
}
}
delete p->m_link;
return *this;
}

template<class T>
MyNodeList<T>& MyNodeList<T>::InsertK(int k, const T &x)


{
Node<T> *p=Head;
Node<T> *q= new Node<T>;
int i;
if (k<1)

{
throw exception();
}

if ( k==1)

{
}
else

{
for (i=1; (p!=0)&&(i!=k); p=p->m_link,i++)

{
}
if ( i!=k)

{
throw exception();
}
}
q->m_data = x;
q->m_link = p->m_link;
p->m_link = q;
return *this;
}

template<class T>
MyNodeList<T>::~MyNodeList()


{
Node<T> *p=Head;
Node<T> *q;
while(p)

{
q=p->m_link;
delete p;
p=q;
}
}

template<class T>
ostream& operator<<(ostream &out,const MyNodeList<T>& mylist)


{
Node<T>* p=mylist.Head;
if (mylist.IsEmpty())

{
return out;
}
while((p=p->m_link))

{
out<<p->m_data<<" ";
}
return out;
}
#endif

模拟链式存储:
它是用int模拟链表节点的指针域而形成的,其基本思想是:每个节点有数据和int型模拟指针组成,申请数组空间,开始设置一个head指向链表的头结点,head=0,其int域为-1,表示链表为空。一个first代表了第一个可用空间的下标,first=1,其int域为2,表示下一个可用空间,以此类推……插入和删除的过程,实际就是设置first和链表的int域的过程。可用户多个链表共享空间。
优点:插入和删除的过程只需要设置int域,不需要移动元素。其销毁链表的速度远高于指针链表,比如,其链表的头尾指针分别为s和e,释放时,只需执行node[e].link=first;first=s;即完成链表释放。
缺点:必须预先分配空间,每个节点需要增加一个int域。
源码:

Code

/**//********************************************************************
created: 2008/05/24
created: 24:5:2008 15:45
filename: MySimpleNode.h
file path:
file base: MySimpleNode
file ext: h
author: CY
purpose: 用简单数据类型模拟链式存储
*********************************************************************/

#ifndef _MYSIMPLENODE_H_
#define _MYSIMPLENODE_H_

#include <iostream.h>
#include <cstdlib>
#include <exception>

template<class T>

struct SimpleNode
{
T data;
int link;
};

template<class T>

class MySimpleNode
{
public:
MySimpleNode (int MaxSpaceSize=100);

~MySimpleNode()
{delete [] node;}
bool IsEmpty() const //判空

{
return (node[m_head].link == -1);
}
int Length() const; //求表长

bool FindK(int k,T& x); //找到第k个元素返回给x

int Search(const T& x); //找出x所在位置

MySimpleNode<T>& DeleteK(int k,T& x); //删除第k个元素,只返回给x

MySimpleNode<T>& InsertK(int k,const T& x);//在第k个元素后插入x

friend ostream& operator<<(ostream& out,const MySimpleNode<T>& my);

private:
int m_first; //可用空间的头结点
int m_head; //模拟链表头结点
SimpleNode<T> *node;//节点数组
};

template<class T>

MySimpleNode<T>::MySimpleNode(int MaxSpaceSize/**//* =100 */)


{
node = new SimpleNode<T>[MaxSpaceSize+1];
m_head = 0;
m_first = 1;
for (int i=1; i<MaxSpaceSize; i++)

{
node[i].link = i+1;
}
node[MaxSpaceSize].link = -1; //-1表示链表中的NULL
node[m_head].link = -1;
}

template<class T>
int MySimpleNode<T>::Length()const


{
int i=0;
int p=node[m_head].link;
while (p!=-1)

{
i++;
p=node[p].link;
}
return i;
}

template<class T>
bool MySimpleNode<T>::FindK(int k,T& x)


{
int p=m_head;
int i=0;
if ( k<1)

{
throw exception();
}
while((p != -1) && i != k)

{
i++;
p=node[p].link;
}
if ( i == k)

{
x = node[p].data;
return true;
}
return false;
}

template<class T>
int MySimpleNode<T>::Search(const T& x)


{
int p=node[m_head].link;
int i=0;
while( (p!=-1)&&(node[p].data!=x))

{
p=node[p].link;
i++;
}
if (node[p].data == x)

{
return i+1;
}
return -1;
}

template<class T>
MySimpleNode<T>& MySimpleNode<T>::DeleteK(int k,T& x)


{
int p=m_head;
int i;
if (k < 1 || p == -1)

{
throw exception();
}
if ( k==1)

{
i=m_first;
}
else

{
for (i=1; (i!=k)&&(p!=-1); i++,p=node[p].link)

{
}
if (i!=k || node[p].link==-1)//位置没找到

{
throw exception();
}
i=m_first;
}
m_first = node[p].link;
node[p].link = node[m_first].link;
node[m_first].link=i;
x = node[node[p].link].data;
return *this;
}

template<class T>
MySimpleNode<T>& MySimpleNode<T>::InsertK(int k,const T& x)


{
int p=m_head;
int i;
if (k<1 || m_first==-1)

{
throw exception();
}

if (k==1)

{
i=m_first;
}
else

{
for (i=1;(i!=k)&&(p!=-1); i++,p=node[p].link)

{
}
if (i!=k)//位置没找到

{
throw exception();
}
i=m_first;
}
m_first = node[i].link;
node[i].data = x;
node[i].link = node[p].link;
node[p].link = i;
return *this;
}

template<class T>
ostream& operator<<(ostream &out,const MySimpleNode<T>& my)


{
int p;
p=my.node[my.m_head].link;
while(p!=-1)

{
out<<my.node[p].data<<" ";
p = my.node[p].link;
}
out<<endl;

return out;
}
#endif

间接寻址: 它是设立二维指针**pptable,其一维数组里存放元素地址,然后按地址存取,如果访问第五个元素,只需取*pptable[4]即可。它结合了顺序表和链表的优点。
优点:查找元素的时间复杂性为o(1),删除或插入元素时,只需要移动指针,所以很好的去除了与元素类型的线性关系。尤其适合大的对象。
缺点:需要预先分配空间。相当于每个节点增加了一个指针大小。
源码:

Code

/**//********************************************************************
created: 2008/05/24
created: 24:5:2008 14:02
filename: MyIndirectList.h
file path:
file base: MyIndirectList
file ext: h
author: CY
purpose: 线性表的间接寻址描述,将顺序表与链式存储结合,降低
各种运算的时间复杂度
*********************************************************************/

#ifndef _MYINDIRECTLIST_H_
#define _MYINDIRECTLIST_H_

#include <iostream.h>
#include <cstdlib>
#include <exception>

template<class T>
class MyIndirectList


{
public:
MyIndirectList(int Maxsize=100) //初始化,分配空间

{
m_size = Maxsize;
m_lenth = 0;
m_ppTable = new T*[m_size];
}
~MyIndirectList();

bool IsEmpty() const //判空

{
return m_lenth==0;
}

int Length() const //求表长

{
return m_lenth;
}

bool FindK(int k, T& x);//找到第k个元素,返回给x

int Search(const T& x); //返回元素x所在位置

MyIndirectList& DeleteK(int k,T& x);//删除第k个元素,将其返回给x

MyIndirectList& InsertK(int k,const T& x);//将元素x插入第k个位置后

friend ostream& operator<<(ostream& out,const MyIndirectList& my);//运算符重载
private:
T **m_ppTable; //建立存储表
int m_lenth; //已存储元素个数
int m_size; //当前容量
};

template<class T>
bool MyIndirectList<T>::FindK(int k, T& x)


{
if (k<1 || k>m_lenth)//第k个元素不存在

{
return false;
}
x = *m_ppTable[k-1];
return true;
}

template<class T>
int MyIndirectList<T>::Search(const T& x)


{
int i=0;
for ( ; i<m_lenth; i++)

{
if (x == *m_ppTable[i]) //找到元素x

{
break;
}
}
if (i == m_lenth)

{
return -1;
}
return i+1;
}

template<class T>
MyIndirectList<T>& MyIndirectList<T>::DeleteK(int k,T& x)


{
if (FindK(k,x)) //删除位置合法

{
for (int i=k; i<m_lenth; i++)//将位置k后的元素前移

{
m_ppTable[i-1] = m_ppTable[i];
}
m_lenth--; //长度减一
return *this;
}
throw exception();//删除位置不合法,抛出异常
}

template<class T>
MyIndirectList<T>& MyIndirectList<T>::InsertK(int k,const T& x)


{
if (k<1 || k>m_lenth+1 ||m_lenth==m_size)//插入位置不合法

{
throw exception();
}

m_ppTable[m_lenth] = new T; //申请空间
for (int i=m_lenth; i>k-1; i--) //插入位置后的元素后移

{
m_ppTable[i]=m_ppTable[i-1];
}
*m_ppTable[k-1] = x; //将x插入
m_lenth++; //长度加一
return *this;
}

template<class T>
MyIndirectList<T>::~MyIndirectList()


{
for (int i=m_lenth-1; i>=0;i--)

{
delete m_ppTable[i];
m_lenth--;
}
delete[] m_ppTable;
}

template<class T>
ostream& operator<<(ostream& out,const MyIndirectList<T>& my)


{
for (int i=0; i<my.m_lenth; i++)

{
out<<*(my.m_ppTable[i])<<" ";
}
out<<endl;
return out;
}
#endif

主程序:

Code

/**//********************************************************************
created: 2008/05/24
created: 22:5:2008 10:15
filename: main.cpp
file path:
file base: main
file ext: cpp
author: CY
purpose: 调用MyArrayList类
*********************************************************************/
#include "MyArrayList.h"

int main()


{
try

{
MyArrayList<int> mylist;
int value;
cout<<"mylist is empty:"<<mylist.IsEmpty()<<endl;
cout<<"mylist length is:"<<mylist.Length()<<endl<<endl;
//插入元素4,3,2,1,0到表中
mylist.InsertK(1,4).InsertK(2,3).InsertK(3,2).InsertK(4,1).InsertK(5,0);
cout<<"mylist is empty:"<<mylist.IsEmpty()<<endl;
cout<<"mylist length is:"<<mylist.Length()<<endl;
cout<<mylist<<endl;
//找出第四个元素,输出
if(mylist.FindK(4,value))
cout<<"The position in 4th is: "<<value<<endl<<endl;
else
cout<<"The position in 4th is invalid!"<<endl<<endl;
//查找元素3的位置,输出
value = mylist.Search(3);
cout<<"The element 3 is in position:"<<value<<endl<<endl;

/**//* 调用错误,删除时先求出当前length,在执行删除操作,当
* 第一个删除执行完后,lenth减一,所以第二个删除的最后
* 一个元素已经不存在,故引发异常
mylist.DeleteK(1,value).DeleteK(mylist.Length(),value);
*/
//正确调用方式
mylist.DeleteK(1,value).DeleteK(mylist.Length()-1,value);
cout<<mylist<<endl;
//引发异常的代码
mylist.InsertK(7,8);
mylist.DeleteK(9,value);
return 0;
}
catch(
)

{
cout<<"Error happened!"<<endl;
return 0;
}
}

各个类的调用方式一致,在调用其它类时,只需修改对象类型。
算法时间复杂度分析:s表示元素类型的大小
存储结构 查找第k个元素 查找元素x 删除第k个元素 在第k个元素后插入
顺序表(MyArrayList) o(1) o(n) o((n-k)s) o((n-k)s)
链表(MyNodeList) o(k) o(n) o(k) o(k+s)
模拟指针(MySimpleNode) o(k) o(n) o(k) o(k)
间接寻址(MyIndirectList)o(1) o(n) o(n-k) o(n-k)
几点注意事项:
1.模板如果应用于复杂数据类型,则应该在此类型中重载!=、==、<<等运算符,因为算法中用到这些运算符。
2.在应用DeleteK或InsertK函数时,可以连续调用,这也是返回自身引用的原因。
3.连续调用时,应当注意函数的执行方式。函数调用时,会先确定语句中每个参数的值,才开始执行操作。举个例子,比如当前表长为5,执行
mylist.DeleteK(1,value).DeleteK(mylist.Length(),value);
是错误的,因为函数的实际执行的是mylist.DeleteK(1,value).DeleteK(5,value);当执行完DeleteK(1,value)时,表长变为4,而接着欲执行DeleteK(5,value),则要删除的第五个元素已不存在。所以会发生异常。正确的调用方式为
mylist.DeleteK(1,value).DeleteK(mylist.Length()-1,value);
Insert函数也有同样的问题。建议在此语句中最好不要使用Length函数。
4.在异常处理上,最好将异常封装成一个类,然后抛出,这里做了简化,没有分清是哪一种异常。
我遇到的问题:
1.在组织文件时,模板类的声明必须与定义放在同一个文件中,这就破环了信息隐藏,不知大家都是怎么组织文件结构的?
2.在链式存储中,程序编译链接均正常,但在插入时,抛出一个异常。我通过调试,发现引起异常的语句是这一句:
Node<T> *q = new Node<T>;
我是想分配一个新节点空间,到现在也没有明白为什么不对,希望高手能不吝指教。