數據結構篇————線性表
線性表的抽象數據類型的定義:
ADT 線性表(List)
Data
線性表的數據對象集合為{a1,a2,....,an},每個元素的類型均為DataType。其中,除了第一個元素a1外,每一個元素有且只有一個直接前驅元素,除最后一個元素an外,每一個元素有且只有一個直接后繼元素。數據元素之間的關系是一對一的關系。
Operation
InitList(*L):初始化操作,建立一個空的線性表。
ListEmpty(L):若線性表為空,返回true,否則返回false。
ClearList(*L):線性表清空。
GetElem(L,i,*e):將線性表L中第i個位置元素返回給e。
LocateElem(L,e):在線性表L中查找與給定值e相等的元素,如果查找成功,返回該元素在表中的序列號;否則,返回0表示失敗。
ListInsert(*L,i,e):在線性表的第i個位置插入元素e。
ListDelete(*L,i,*e):刪除線性表L中的第i個元素,並用e返回其值
ListLength(L):返回線性表L的元素個數。
PrintList(L):打印線性表
對於不同的應用,線性表的基本操作是不同的,上述操作是最基本的。
對於實際問題中涉及的關於線性表的更復雜操作,完全可以用這些基本操作的組合來實現。
線性表順序存儲
順序表,一般使用數組實現,事實上就是在內存中找個初始地址,然后通過占位的形式,把一定連續的內存空間給占了,然后把相同數據類型的數據元素依次放在這塊空地中,數組大小有兩種方式指定,一是靜態分配,二是動態擴展。
線性表我用類模板來實現,提高實用性
const int maxx=100;
template <class datatype>
class seqlist
{
private:
datatype data[maxx];
int lenght;
public:
seqlist(){lenght=0;};//無參數的構造函數
seqlist(datatype a[],int n);//有參數的構造函數
~seqlist(){}//析構函數,直接析構。
int _lenght(){return lenght;}//返回鏈表的當前長度。
datatype _getvalue(int i);//得到第i個元素的值
int _location(datatype data1);//返回元素value的位置
void _insert(int i,datatype value);//插入一個參數
datatype _delete(int i);//刪除一個元素
void _printf();//遍歷線性表的每一個元素。
};
順序表的封裝需要三個屬性:
- 存儲空間的起始位置。數組data的存儲位置就是線性表存儲空間的存儲位置
- 線性表的最大存儲容量。數組長度maxx
- 線性表的當前長度。length
注意:數組的長度與線性表的當前長度是不一樣的。數組的長度是存放線性表的存儲空間的總長度,一般初始化后不變。而線性表的當前長度是線性表中元素的個數,是會改變的。
通過數組初始化有參構造函數
template <class datatype>
seqlist<datatype>::seqlist(datatype a[],int n)
{
if (n>maxx) cout<<"錯誤"<<endl;
else
{
for(int i=0;i<n;i++)
{
data[i]=a[i];
//p++;
}
lenght=n;
}
}
元素的查找
- 按位查找的時間復雜度為O(1) 。
template <class datatype>
datatype seqlist<datatype>::_getvalue(int n)
{
if(n<1 && n>lenght) {cout<<"出錯"<<endl; return -1;}
else return data[n-1];
}
- 按值查找,需要對順序表中的元素依次進行比較。
template <class datatype>
int seqlist<datatype>::_location(datatype data1)
{
for(int i=0;i<lenght;i++)
{
if(data[i]==data1) return i+1;
//else return -1;
}
return -1;
}
數據的插入操作
void seqlist<datatype>::_insert(int i,datatype value)
{
if(lenght>=maxx) cout<<"出錯";
int j;
if(i<1||i>lenght+1) cout<<"出錯";
for(j=lenght;j>i-1;j--)
data[j]=data[j-1];
data[j]=value;
lenght++;
}
數據的刪除操作
template <class datatype>
datatype seqlist<datatype>::_delete(int i)
{
int x;
if(lenght==0) cout<<"出錯";
if(i<1 || i>lenght) cout<<"出錯";
x = data[i-1];
for(int j=i;j<lenght;j++)
data[j-1] = data[j];
lenght--;
return x;
}
數據的遍歷
template <class datatype>
void seqlist<datatype>::_printf()
{
for(int i=0;i<lenght;i++)
cout<<data[i]<<endl;
}
主函數的實現操作
int main()
{
//本節點參考了如下文章:https://blog.csdn.net/qq_30611601/article/details/79516986#%E4%BA%94%E5%85%B6%E4%BB%96%E7%BA%BF%E6%80%A7%E8%A1%A8
int a[]={1,2,3,4,5,6,7,8,9,10},m=10;
seqlist<int> p(a,m);
p._insert(5,13);
p._insert(12,20);
cout<<p._getvalue(3)<<endl;
cout<<p._lenght()<<endl;
cout<<p._location(20)<<endl;
p._delete(7);
cout<<p._lenght()<<endl;
p._printf();
return 0;
}
優點:
隨機訪問特性,查找O(1)時間,存儲密度高;
邏輯上相鄰的元素,物理上也相鄰;
無須為表中元素之間的邏輯關系而增加額外的存儲空間;
缺點:
插入和刪除需移動大量元素;
當線性表長度變化較大時,難以確定存儲空間的容量;
造成存儲空間的“碎片”
線性表的鏈式存儲
線性表的鏈式存儲結構的特點是用一組任意的存儲單元存儲線性表的數據元素,這組存儲單元可以是連續的,也可以是不連續的。這就意味着,這些元素可以存在內存未被占用的任意位置,鏈表的定義是遞歸的,它或者為空null,或者指向另一個節點node的引用,這個節點含有下一個節點或鏈表的引用,線性鏈表的最后一個結點指針為“空”(通常用NULL或“^”符號表示)
存儲的實現
結點由存放數據元素的數據域和存放后繼結點地址的指針域組成。
struct node //結點結構
{ int data ;
node * next;
//Node<DataType>這個和node沒有區別,唯一的區別是node結構體中的數據類型都為DataType,學會了這種的同學可以試着把int數據類型變為類模板實現。
};
*單鏈表的類的實現 *
class linklist
{
private:
node *first;
public:
linklist();//無參構造,生成一個只有頭結點的空鏈表
linklist(int a[],int n);//有參構造。
~linklist();//析構函數
int _length();//返回鏈表的長度
int _get(int i);//返回第i個元素的值
int _locate(int num);//返回值num的序號,從1開始。
void _insret(int i,int num);//中間插入元素:i表示第i個元素,元素的值為num.
int _Delete(int i);//刪除第i個元素的值。
void _print ();//遍歷整個鏈表。
};
無參數構造
只生成一個一個空的頭結點。代碼實現:
linklist::linklist()//構建空鏈表
{
first = new node;
first->next = NULL;
}
有參數的構造———頭插法構造
頭插法是每次將新申請的結點插在頭結點后面 ,代碼實現:
linklist::linklist(int a[],int n)//頭插法插入鏈表
{
first =new node ;//
first->next=NULL;//這兩行是頭結點,next指向空。
for(int i=0;i<n;i++)
{
node *s=new node;
s->data=a[i];
s->next=first->next;
first->next=s;
}
}
有參數的構造———尾插法構造
尾插法就是每次將新申請的結點插在終端節點的后面 ,代碼實現:
linklist::linklist(int a[],int n)//尾插法就是每次將新申請的結點插在終端節點的后面
{
first=new node ;
node *p=first;
for(int i=0;i<n;i++)
{
node *s=new node;
s->data=a[i];
p->next=s;
p=s;//把s節點賦值給p節點。
}
p->next=NULL;//鏈表的最后一個節點的指針為空。
}
析構函數
單鏈表類中的結點是用new申請的,在釋放的時候無法自動釋放,所以,析構函數要將單鏈表中的結點空間釋放。
linklist::~linklist()
{
if(first!=NULL)
{
node *p=first;
first=first->next;
delete p;
}
}
計算鏈表的長度
單鏈表中不能直接求出長度,所以我們只能將單鏈表掃描一遍,所以時間復雜度為O(n)。
int linklist::_length()
{
node *p=first->next;
int count=0;
while(p!=NULL)
{
p=p->next;
count++;
}
return count;
}
查找第i個元素的值
單鏈表中即使知道節點位置也不能直接訪問,需要從頭指針開始逐個節點向下搜索,平均時間性能為O(n)
int linklist::_get(int i)
{
node *p=first->next;
int ocunt =1;
while(p!=NULL&&ocunt<i)
{
p=p->next;
ocunt++;
}
if(p==NULL) {return 0;cout<<"不存在"<<endl;}
else return p->data;
}
查找元素a,返回元素的位置
單鏈表中按值查找與順序表中的實現方法類似,對鏈表中的元素依次進行比較,平均時間性能為O(n)
int linklist::_locate(int num)
{
node *p=first->next;
int count=1;
while(p!=NULL)
{
if(p->data==num) return count;
p=p->next;
count ++;
}
return 0;
}
插入元素
單鏈表在插入過程中需要注意分析在表頭、表中間、表尾的三種情況,由於單鏈表帶頭結點,這三種情況的操作語句一致,不用特殊處理,時間復雜度為O(n)。
void linklist::_insret(int i,int num)//i表示第i個元素,元素的值為num.
//單鏈表在插入過程中需要注意分析在表頭、表中間、表尾的三種情況,由於單鏈表帶頭結點,
//這三種情況的操作語句一致,不用特殊處理,時間復雜度為O(n)
{
node *p=first;
int count=0;
while(p!=NULL&&count<i-1)
{
p=p->next;
count++;
}
if(p==NULL) cout<<"錯誤";
else
{
node *s= new node ;
s->data=num;
s->next=p->next;
p->next=s;
}
}
刪除操作
刪除操作時需要注意表尾的特殊情況,此時雖然被刪結點不存在,但其前驅結點卻存在。因此僅當被刪結點的前驅結點存在且不是終端節點時,才能確定被刪節點存在,時間復雜度為O(n) .
int linklist::_Delete(int i)
{
node *p=first;
int count =0;
while(p!=0&&count<i-1)
{
p=p->next;
count++;
}
if(p==NULL){cout<<"不存在";
return 0;}
else
{
node *s=p->next;
int x=s->data;
p->next=s->next;
return x;
}
}
遍歷元素
遍歷單鏈表時間復雜度為O(n) .
void linklist::_print ()
{
node *p=first->next;
while(p!=NULL)
{
cout<<p->data<<endl;
p=p->next;
}
}