數據結構(03)_順序存儲結構線性表
14.線性表的本質和操作
14.1.線性表的表現形式
- 零個多多個數據元素組成的集合
- 數據元素在位置上是有序排列的
- 數據元素的個數是有限的
- 數據元素的類型必須相同
14.2.線性表的抽象定義、性質
線性表是具有相同類型的n(>=)個數據元素的有限序列,(a0, a1, a2... an-1),其中ai是表項,n是表長度。
性質: - a0為線性表的第一個元素,只有一個後繼
- an-1為線性表的最後一個元素,只有一個前驅
- 其他數據項既有後繼,也有前驅
- 支持逐項和順序存儲
14.3.線性表的一些常用操作
- 插入、刪除數據元素
- 獲取、設置目標位置元素的值
- 獲取線性表的長度
- 清空線性表
示例代碼:template <typename T> class list : public Object { public: virtual bool insert(int index, const T& e) = 0; virtual bool remove(int index) = 0; virtual bool set(int index, const T& e) = 0; virtual bool get(int index, T& e) const = 0; virtual bool length() const = 0; virtual bool clear() = 0; }
14.4.總結:
線性表是數據元素的有序並且有限的集合,其中的數據元素類型相同,在程序中表現為一個特殊的數據結構,可以使用C++中的抽象類來表示,用來描述排隊關系的問題。
15.線性表的順序存儲結構
15.1.概念和設計思路
定義:
線性表的順序存儲結構,指的是用一段地址連續的存儲單元依次存儲線性表中的數據元素。
設計思路:
使用一維數組來實現存儲結構:// 存儲空間:T* m_array; 當前長度:int m_length; template <typename T> class SeqList : public List<T> { protected: T* m_array; int m_length; };
15.2.順序存儲結構的元素操作
- 獲取:判斷目標位置是否合法,將目標位置做為數組下標獲取元素。
- 插入:1.判斷目標位置是否合法,將目標位置之後的元素後移一個位置,3.將新元素插入目標位置,4.線性表長度加1。(註意插入的點永遠比元素會多一個)
- 刪除:1.判斷目標位置是否合法,將目標位置之後的元素前移一個位置,3.線性表長度減1。
15.3 List實現
template <typename T>
class List:public Object
{
protected:
List(const List&);
List& operator ==(const List&);
public:
List(){}
virtual bool insert(const T& e) = 0;
virtual bool insert(int i,const T& e) = 0;
virtual bool remove(int i) = 0;
virtual bool set(int i,const T& e) = 0;
virtual bool get(int i,T& e) const = 0;
virtual int length() const = 0;
virtual void clear() = 0;
};
16.SeqList的設計要點
- 抽象類模板,存儲空間的大小和位置由子類完成;
- 實現順序存儲結構線性表的關鍵操作(增、刪、查、等);
-
提供數組操作符重載,方面快速獲取元素;
template <typename T> class SeqList : public List<T> { protected: T* m_array; // 順序存儲空間 int m_length; // 當前線性長度 public: bool insert(int index, const T& e); bool remove(int index); bool set(int index, const T& e); bool get(int index, T& e) const; int length() const; void clear(); // 順序存儲表的數組訪問方式 T& operator [] (int index); T operator [] (int index) const; // 順序存儲表的的容量 virtual int capacity() const = 0; };
思考:StaticList和DynamicList如何實現,差異在那裏?是否可以將DynamicList做為StaticList的子類實現
這兩者的差異在於,後者可以動態指定線性表的大小和存儲空間,由於兩者的性質完全不同,所以不能實現為彼此的子類16.1SeqList實現
template <typename T>
class SeqList : public List<T>
{
protected:
T* m_array; // 順序存儲空間
int m_length; // 當前線性長度
public:
bool insert(int index, const T& e)
{
bool ret = ( (index>=0) && (index<=m_length) ); // <= 因為可以插入的點,必然比當前元素多1
if(ret && ( m_length < capacity() )) // 當前至少有一個空間可插入
{
for(int p=m_length-1; p>=index; p--)
{
m_array[p + 1] = m_array[p];
}
m_array[index] = e;
m_length++;
}
return ret;
}
bool insert(const T& e)
{
return insert(m_length, e);
}
bool remove(int index)
{
bool ret = ( (index>=0) && (index<m_length) ); // 目標位置合法 <m_length
if(ret)
{
for(int p=index; p<m_length-1; p++) // 註意思考此處的邊界條件
{
m_array[p] = m_array[p+1];
}
m_length--;
}
return ret;
}
bool set(int index, const T& e)
{
bool ret = ( (index>=0) && (index<m_length) );
if(ret)
{
m_array[index] = e;
}
return ret;
}
bool get(int index, T& e) const
{
bool ret = ( (index>=0) && (index<m_length) );
if(ret)
{
e = m_array[index];
}
return ret;
}
int length() const
{
return m_length;
}
void clear()
{
m_length = 0;
}
// 順序存儲表的數組訪問方式
T& operator [] (int index)
{
if( (index>=0) && (index<m_length) )
{
return m_array[index];
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException, "index out of range...");
}
}
T operator [] (int index) const
{
static_cast<SeqList<T>&>(*this)[index]; // 去除const屬性,然後調用非const版本實現
}
// 順序存儲表的的容量
virtual int capacity() const = 0;
};
}
#endif // SEQLIST_H
17.StaticList和DynamicList
17.1.StaticList的設計要點:
類模板
- 使用原生數組做為順序存儲空間
- 使用模板參數決定數組的大小
template < typename T, int N > class StaticList : public SeqList <T> { protected: T m_space[]; // 順序存儲空間,N為模板參數 public: StaticList(); // 指定父類成員的具體值 int capacity() const; };
17.1.1 StaticList實現
template < typename T, int N >
class StaticList : public SeqList <T>
{
protected:
T m_space[N]; // 順序存儲空間,N為模板參數
public:
StaticList() // 指定父類成員的具體值
{
this->m_array = m_space;
this->m_length = 0;
}
int capacity() const
{
return N;
}
};
17.2.DynamicList的設計要點:
類模板
- 申請連續堆空間做為順序存儲空間
- 保證重置順序存儲空間的異常安全性
函數異常安全的概念: - 不允許任何內存泄露,不允許破壞數據
- 函數異常安全的基本保證:
- 如果有異常拋出,對象內的任何成員任然能保持有效狀態,沒有數據破話或者資源泄露。
template < typename T> class DynamicList : public SeqList <T> { protected: int capacity; // 順序存儲空間的大小 public: DynamicList(int capacity); // 申請空間 int capacity(void) const // 返回capacity的值 // 重置存儲空間的大小 void reset(int capacity); ~DynamicList(); // 歸還空間 };
17.2.1 DynamicList實現
template <typename T>
class DynamicList : public SeqList<T>
{
protected:
int m_capacity;
public:
DynamicList(int capacity)
{
this->m_array = new T[capacity];
if(this->m_array != NULL)
{
this->m_length = 0;
this->m_capacity = capacity;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"No memory to create DynamicList object ...");
}
}
int capacity()const
{
return m_capacity;
}
void resize(int capacity)
{
if(capacity != m_capacity)
{
T* array = new T[capacity];
if(array != NULL)
{
int length = (this->m_length < capacity ? this->m_length : capacity);
for(int i=0;i<length;i++)
{
array[i] = this->m_array[i];
}
T* temp = this->m_array;
this->m_array = array;
this->m_length = length;
this->m_capacity = capacity;
delete[] temp;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"No memory to create DynamicList object ...");
}
}
}
~DynamicList()
{
delete[] this->m_array;
}
};
18.順序存儲結構線性表分析
18.1.時間復雜度
順序存儲結構線性表的效率為O(n),主要受其插入和刪除操作的影響(譬如插入操作時,要插入位置之後的數據要向後挪動) 。
18.2.問題
兩個長度相同的順序存儲結構線性表,插入、刪除操作的耗時是否相同?
不相同,對順序存儲結構線性表,其插入、刪除操作的復雜度還取決於存儲的數據類型,譬如一個普通類型和一個字符串類型/類類型就完全不同(對於復雜數據類型,元素之間移動時必然耗時很多)。從這個角度考慮,線性表的效率存在隱患。
18.3.禁用拷貝構造和賦值操作。
貝構造和賦值操作會導致兩個指針指向同一個地址,導致內存重復釋放。對於容器類型的類,可以考慮禁用拷貝構造和賦值操作。
原因: 1、對於生活中容器類的東西,我們無法對其進行賦值(譬如生活中我們不可能將杯子中的水進行復制,只能使用另一個杯子重新去獲取等量的水)。
實現:將拷貝構造和賦值操作函數定義為proteced成員,在類的外部,不能使用。
protected:
List(const List&){}
List& operator = (const List&){}
18.4.線性表不能直接當做數組來使用
順序存儲結構線性表提供了數組操作符的重載,可以直接像數組一樣,同過下標直接獲取目標位置的元素,在具體的使用上類似數組,但是本質上不同,不能代替數組使用:
- 必須先進行插入操作,才能對其內部的數據進行操作。
- 原生數組是自帶空間的,可以直接操作。
數據結構(03)_順序存儲結構線性表