下面介绍常用的四种存储方式,附上源码:
顺序表:这是大家最为熟悉的一种描述,相对比较简单。
优点:存储空间小,不需要额外增加空间,元素按位置查找方便,速度快。
缺点:必须预先分配空间,在删除和插入操作时,需要移动元素,因此时间复杂度与元素大小成线性关系,面对大型类对象时,此缺点是致命的。
源码:
/**//********************************************************************
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
链表:
优点:删除和插入操作不需要移动元素,表长可动态改变。
缺点:按位置查找需要遍历链表,存储需要额外的空间。
源码:
/**//********************************************************************
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域。
源码:
/**//********************************************************************
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),删除或插入元素时,只需要移动指针,所以很好的去除了与元素类型的线性关系。尤其适合大的对象。
缺点:需要预先分配空间。相当于每个节点增加了一个指针大小。
源码:
/**//********************************************************************
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
主程序:
/**//********************************************************************
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>;
我是想分配一个新节点空间,到现在也没有明白为什么不对,希望高手能不吝指教。