第3章 資料描述 最常見的資料描述方法有:公式化描述、連結描述、間接定址和類比指標。 公式化描述藉助數學公式來確定元素表中的每個元素分別儲存在何處(如儲存空間地址)。 最簡單的情形就是把所有元素依次連續儲存在一片連續的儲存空間中,這就是通常所說的連續線性表。 在連結描述中,元素表中的每個元素可以儲存在儲存空間的不同地區中,每個元素都包含一個指向下一個元素的指標。同樣,在間接定址方式中,元素表中的每個元素也可以儲存在儲存空間的不同地區中,不同的是,此時必須儲存一張表,該表的第 i項指向元素表中的第 i個元素,所以這張表是一個用來儲存元素地址的表。 在公式化描述中,元素地址是由數學公式來確定的;在連結描述中,元素地址分布在每一個表元素中;而在間接定址方式下,元素地址則被收集在一張表中。 類比指標非常類似於連結描述,區別在於它用整數代替了 C++指標,整數所扮演的角色與指標所扮演的角色完全相同。1. 公式化描述的評價 這種描述方法的一個缺點是空間的低效利用。考察如下的情形:我們需要維持三個表,而且已經知道在任何時候這三個表所擁有的元素總數都不會超過 5000個。然而,很有可能在某個時刻一個表就需要5000個元素,而在另一時刻另一個表也需要 5000 個元素。若採用類LinearList,這三個表中的每一個表都需要有 5000個元素的容量。因此,即使我們在任何時刻都不會使用5000以上的元素,也必須為此保留總共 15000個元素的空間。 為了避免這種情形,必須把所有的線性表都放在一個數組 list中進行描述,並使用兩個附加的數組first和last對這個數組進行索引。圖 3-3給出了在一個數組 list中描述的三個線性表。我們採用大家很習慣的約定,即如果有 m個表,則每個表從1到m進行編號,且 first[i]為第i個表中的第一個元素。有關 first[i]的約定使我們更容易地採用公式化描述方式。 last[i]是表i的最後一個元素。注意,根據這些約定,每當第 i個表不為空白時,有 last[i]>first[i],而當第i個表為空白時,有last[i]=first[i]。所以在圖3-3的例子中,表 2是空表。在數組中,各線性表從左至右按表的編號次序1,2,3,...,m進行排列。
公式化描述代碼如下:
View Code #include <iostream>
#include <stdexcept>
#include <cstdlib>
#include <ctime>
using namespace std;
template <class T>
class LinearList{
public:
LinearList(int MaxListSize=10);
~LinearList(){
delete[] element;
}
LinearList(const LinearList<T>& ll);
LinearList(T*first ,T* last);
LinearList<T>& operator=(const LinearList<T>& ll);
bool IsEmpty()const{
return length==0;
}
int Length()const{
return length;
}
bool Find(int k,T &x)const;
int Search(const T& x)const;
LinearList<T>& Delete(int k,T& x);
LinearList<T>& Insert(int k,const T& x);
void Output(ostream& os)const;
private:
int length;
int MaxSize;
T* element;
};
template <class T>
LinearList<T>::LinearList(int MaxListSize){
element=new T[MaxListSize];
length=0;
MaxSize=MaxListSize;
}
template <class T>
LinearList<T>::LinearList(const LinearList<T>& ll){
MaxSize=ll.MaxSize;
length=ll.length;
element=new T[MaxSize];
for(int i=0;i<length;i++)
element[i]=ll.element[i];
}
template <class T>
LinearList<T>::LinearList(T* first,T* last){
int sz=last-first;
MaxSize=sz;
length=sz;
element=new T[MaxSize];
for(int i=0;i<length;i++)
element[i]=first[i];
}
template <class T>
LinearList<T>& LinearList<T>::operator=(const LinearList<T>& ll){
if(this!=&ll){
if(MaxSize==ll.MaxSize) {
length=ll.length;
for(int i=0;i<length;i++)
element[i]=ll.element[i];
}
else{
delete[] element;
MaxSize=ll.MaxSize;
length=ll.length;
element=new T[MaxSize];
for(int i=0;i<length;i++)
element[i]=ll.element[i];
}
}
return *this;
}
template <class T>
bool LinearList<T>::Find(int k,T& x)const
{
if(k<1||k>length)
return false;
x=element[k-1];
return true;
}
template<class T>
int LinearList<T>::Search(const T& x)const
{
int i;
for(i=0;i<length&&element[i]!=x;i++);
if(i<length)
return ++i;
else
return 0;
}
template<class T>
LinearList<T>& LinearList<T>::Delete(int k,T& x)
{
if(!Find(k,x))
throw out_of_range("OutOfBounds");
for(int i=k-1;i<length-1;i++)
element[i]=element[i+1];
length--;
return *this;
}
template<class T>
LinearList<T>& LinearList<T>::Insert(int k,const T& x)
{
if(k<0||k>length)
throw out_of_range("OutOfBounds");
if(length==MaxSize)
throw out_of_range("NoMem");
length++;
for(int i=length-1;i>k;i--)
element[i]=element[i-1];
element[k]=x;
return *this;
}
template<class T>
void LinearList<T>::Output(ostream& os)const
{
for(int i=0;i<length;i++)
os<<element[i]<<" ";
}
template<class T>
ostream& operator<<(ostream& os,const LinearList<T>& ll){
ll.Output(os);
return os;
}
int main()
{
try{
LinearList<int> L(5);
cout << "Length = " << L.Length() << endl;
cout << "IsEmpty = " << L.IsEmpty() <<endl;
L.Insert(0,2).Insert(1,6);
cout << "List is " << L <<endl;
cout << "IsEmpty = " << L.IsEmpty() << endl;
int z;
L.Find(1,z);
cout << "First element is " << z << endl;
cout << "Length = " << L.Length() << endl;
L.Delete(1,z);
cout << "Deleted element is " << z << endl;
cout << "List is " << L << endl;
srand(time(0));
int n=10;
int* arr=new int[n];
for(int i=0;i<n;i++)
arr[i]=rand()%100;
LinearList<int> l2(arr,arr+n);
cout<<"l2:"<<l2<<endl;
}
catch (exception& e){
cerr << "An exception has occurred:" <<e.what()<< endl;
}
}
為了避免第一個表和最後一個表的處理方法與其他的表不同,定義了兩個邊界表:表 0和表m+1,其中first[0]=last[0]=-1, first[m+1]=last[m+1]=MaxSize-1。為了在第 i 個表的第 k 個元素之後插入一個元素,首先需要為新元素建立空間。如果last[i]=first[i+1],則在第 i 個表和第 i + 1個表之間沒有空間,因此不能把第 k + 1至最後一個元素向後移動一個位置。在這種情況下,通過檢查關係式 last[i-1]<first[i]是否成立,可以確定是否有可能把第i 個表的1至k-1元素向前移一個位置;如果這個關係式不成立,要麼需要把表 1至表i-1的元素向前移一個位置,要麼把表 i+1至表m 向後移一個位置,然後為表 i建立需要增長的空間。當表中所有的元素總數少於 MaxSize時,這種移位的方法是可行的。圖3-4是一個偽 C++函數,它向表 i 中插入一個新的元素,可以把該函數細化為相容的 C++代碼。