線性表是最常用最典型的線性結構。
簡易目錄:
線性表:
邏輯特徵
線性表的類型定義
存儲結構
順序存儲表示
元素存儲位置的計算
順序表的基本操作實現
順序表各算法時間複雜度的計算
C++中的參數傳遞
鏈式存儲表示
單鏈表
雙向鏈表
循壞鏈表
單鏈表,循壞鏈表和雙向鏈表的時間效率的比較
順序表和鏈表的比較
線性表的合併
用順序表實現
用鏈表實現
可以看到,線性表是最基礎,也是最簡單的了。所以我們從線性表開始學習。
定義:具有相同特性的數據元素的一個有限序列,例如:
邏輯特徵:
線性表的類型(即抽象數據類型,也稱數據類型)定義:
上述的基本操作是邏輯結構上定義的運算,那麼如何實現這些基本運算,就要先確定其存儲結構。
線性表的存儲以及在存儲結構上各操作的實現:
線性表的基本操作:
操作算法中用到的預定義變量和類型:
線性表的存儲結構有四種,其中最基本兩種的存儲結構:順序存儲結構和鏈式存儲結構
線性表的順序存儲表示(也稱爲順序映像):
定義:把邏輯上相鄰的數據元素存儲在物理上相鄰的存儲單元中。
也就是說線性表順序存儲我們可以用一維數組來表示,但由於數組長度不可動態定義
即:
//錯誤寫法
int n;
scanf("%d",&n);
int a[n];
這樣是不可行的,所以要想用一維數組來表示,就必須再定義一個長度,像以上一樣用結構體,在裏面定義一個長度就好了。
元素存儲位置的計算:
我們一般用的都是第二個公式,因爲一般第一個元素的地址(即基地址)我們都知道。
順序表的基本操作實現:
#include<stdio.h>
#include<iostream>
using namespace std;
//1.malloc(m):開闢m字節長度的地址空間,並返回這段空間的首地址
//2.sizeof(x):計算變量x的長度
//3.free(p)函數:釋放指針p所指變量的存儲空間,即徹底刪除一個變量
//以上3個函數都需用到下面的這個頭文件
#include<stdlib.h>
//函數的結果狀態代碼
#define OVERFLOW -2
#define OK 1
#define TRUE 1
#define FALSE 0
#define ERROR 0
//定義
#define MAXSIZE 100
typedef struct{
int *elem; //(數組動態分配)//int elem[MAXSIZE];(數組靜態分配)
int length;
}SqList;
int InitList(SqList &L);//初始化操作,建立一個空的線性表L
void DestroyList(SqList &L);//銷燬線性表L
void ClearList(SqList &L);//清空線性表L
int ListInsert(SqList &L,int i,int e);//在線性表L中第i個位置插入新元素e
int ListDelete(SqList &L,int i,int &e);// 刪除L中第i個位置的元素,並用e返回其值
bool ListEmpty(SqList L);//判斷線性表是否爲空,若爲空,返回true,否則,返回false
int ListLength(SqList L);//返回線性表L中的元素個數
int LocateElem(SqList L,int e);//L中查找與給定值e相等的元素,若成功,則返回該元素在表中的序號,否則返回0
int GetElem(SqList L,int i,int &e);//將線性表L中第i個位置的元素返回給e
int main()
{
SqList L;//創建線性表L
int m=InitList(L);
if(m)
cout<<"成功創建空表L"<<endl;
ListInsert(L,1,3);
cout<<"已成功插入"<<endl;
cout<<"插入後的長度:"<<L.length<<endl;
ListInsert(L,2,6);
cout<<"已成功插入"<<endl;
cout<<"插入後的長度:"<<L.length<<endl;
int e;
ListDelete(L,1,e);
cout<<"被刪除的元素:"<<e<<endl;
cout<<"刪除後的長度:"<<L.length<<endl;
ListEmpty(L);
if(ListEmpty(L))
cout<<"線性表L爲空"<<endl;
else
cout<<"線性表L非空"<<endl;
int n;
ListLength(L);
cout<<"此時線性表的長度:"<<ListLength(L)<<endl;
int a=6;
LocateElem(L,a);
cout<<"該元素的序號爲:"<<LocateElem(L,a)<<endl;
GetElem(L,1,e);
cout<<"線性表L的第1個元素:"<<e<<endl;
ClearList(L);
cout<<"線性表L已清空"<<endl;
DestroyList(L);
cout<<"線性表L已銷燬"<<endl;
return 0;
}
//初始化(即建立一個空的順序表)
int InitList(SqList &L)
{
L.elem=new int[MAXSIZE]; // 用C++來爲順序表分配空間
// L.elem=(int*)malloc(sizeof(int)*MAXSIZE); //用C語言來爲順序表分配空間
if(!L.elem) exit(OVERFLOW); //exit表程序終止 OVERFLOW可看作“溢出”
L.length=0;
return OK;
}
//銷燬線性表L
void DestroyList(SqList &L)
{
if(L.elem) delete L.elem;
}
//清空線性表L
void ClearList(SqList &L)
{
L.length=0;
}
//在線性表L中第i個位置插入新元素e
int ListInsert(SqList &L,int i,int e)
{
if(i<1||i>L.length+1) return ERROR;//插入位置不合法,可以插入在第1到n個位置,也可以插入到表最後(即第n+1個位置)
if(L.length==MAXSIZE) return ERROR;//當前存儲空間已滿
for(int j=L.length-1;j>=i-1;j--)
{
L.elem[j+1]=L.elem[j]; //插入位置及以後的元素後移
}
L.elem[i-1]=e; //將新元素放到第i個位置上,即下標爲i-1的位置上
L.length++;
//平均移動次數n/2,時間複雜度O(n)
}
//刪除L中第i個位置的元素,並用e返回其值
int ListDelete(SqList &L,int i,int &e)
{
if( (i<1) || (i>L.length) ) return ERROR;
e = L.elem[i-1];
for(int j=i ;j<=L.length-1 ;j++)
L.elem[j-1]=L.elem[j]; //被刪除元素之後的元素都前移
--L.length;
return OK;
//平均移動次數(n-1)/2,時間複雜度O(n)
}
//判斷線性表是否爲空
bool ListEmpty(SqList L)
{
if(L.length==0) return true;
else
return false;
}
//返回線性表L中的元素個數
int ListLength(SqList L)
{
return(L.length);
}
//在L中查找與e相等的元素,並返回該元素在表中的序號
int LocateElem(SqList L,int a)
{
for(int i;i<L.length;i++)
if(L.elem[i]==a) return i+1; //查找成功,返回序號
return 0; //查找失敗,返回0
//平均查找次數(n+1)/2,時間複雜度O(n)
}
//將線性表L中第i個位置的元素返回給e
int GetElem(SqList L,int i,int &e)
{
if(i < 1 || i > L.length) return ERROR; //i值不合理
e=L.elem[i-1];
return OK;
//時間複雜度O(1)
}
運行結果:
順序表各算法時間複雜度的計算:
1.插入:
所以時間複雜度就爲O(n)。(如果不懂,可以參考第一章緒論關於時間複雜度的分析)
2.刪除:
所以時間複雜度爲O(n)。
3.查找:
時間複雜度O(n)。
同理:
若查找的是第一個元素,則查找1次就行了
若查找的是第n個元素或查找不成功,則都需要查找n次
平均查找次數:(1/n)*((1+n)*n)/2)=(n+1)/2
C++中的參數傳遞:(主要解釋代碼實現中&的含義)
1.指針變量做參數:
#include<iostream>
using namespace std;
void swap(int *m,int *n);
void swap2(int *x,int *y);
int main()
{
int a=3,c=3;
int b=5,d=5;
swap(&a,&b);
swap2(&c,&d);
cout<<a<<" "<<b<<endl;
cout<<c<<" "<<d<<endl;
return 0;
}
//形參變化影響實參
void swap(int *m,int *n)
{
int t;
t=*m;
*m=*n;
*n=t;
}
//形參變化不影響實參
void swap2(int *x,int *y)
{
int *t;
t=x;
x=y;
y=t;
}
運行結果:
2.數組名作參數:
#include<iostream>
using namespace std;
void sub(char b[]);
int main()
{
char a[10]="hello";
sub(a);
cout<<a<<endl;
}
void sub(char b[])
{
b[0]='w';
}
運行結果:
3.引用類型做參數:
#include<iostream>
using namespace std;
void swap(int &m,int &n);
int main()
{
int a=3;
int b=5;
swap(a,b);
cout<<a<<" "<<b<<endl;
}
void swap(int &m,int &n)
{
int t;
t=m;
m=n;
n=t;
}
運行結果:
可見,我們要修改原來的值時,可用& ,就如順序表代碼實現時,用&L和L的區別就在於L是否被修改。
線性表的鏈式存儲表示:
定義:
單鏈表:
頭結點的作用:
頭結點的數據域:
單鏈表的特點:
單鏈表的基本操作以及代碼實現:(帶頭結點)
#include<iostream>
using namespace std;
//函數的結果狀態代碼
#define OK 1
#define ERROR 0
typedef int ElemType ;//ElemType就相當於int
//定義
typedef struct LNode{ //聲明結點的類型和指向結點的指針類型
ElemType data; //結點的數據域
struct LNode *next; //結點的指針域 ,指向的仍然是這樣的一個結構體
}LNode,*LinkList; //LinkList爲指向結構體Lnode的指針類型
int InitList(LinkList &L);//單鏈表的初始化,即構造一個空表
void CreatList(LinkList &L,int n);//頭插法建立單鏈表
void CreatList2(LinkList &L,int n);//尾插法建立單鏈表
int DestroyList(LinkList &L);//銷燬單鏈表L
int ClearList(LinkList &L);//清空單鏈表L
int ListInsert(LinkList &L,int i,int e);//在單鏈表L中第i個元素之前插入新元素e
int ListDelete(LinkList &L,int i,int &e);// 刪除L中第i個數據元素,並用e返回其值
bool ListEmpty(LinkList L);//判斷單鏈表是否爲空,若爲空,返回true,否則,返回false
int ListLength(LinkList L);//求單鏈表的表長
int LocateElem(LinkList L,int e);//L中查找與給定值e相等的元素,若成功,則返回該元素在表中的序號,否則返回0
LNode *LocateElem2(LinkList L,int e);//L中查找與給定值e相等的元素,若成功,則返回該元素的地址,否則返回NULL
int GetElem(LinkList L,int i,int &e);//將單鏈表L中第i個位置的元素返回給e
int main()
{
LinkList L; //或LNode *L;
int m=InitList(L);
if(m)
cout<<"成功創建空表L"<<endl;
CreatList(L,2);
cout<<"頭插法創建單鏈表成功"<<endl;
CreatList2(L,2);
cout<<"尾插法創建單鏈表成功"<<endl;
ListInsert(L,1,6);
cout<<"已成功插入"<<endl;
ListInsert(L,2,9);
cout<<"已成功插入"<<endl;
int e;
ListDelete(L,1,e);
cout<<"被刪除的元素:"<<e<<endl;
ListEmpty(L);
if(ListEmpty(L))
cout<<"單鏈表L爲空"<<endl;
else
cout<<"單鏈表L非空"<<endl;
ListLength(L);
cout<<"此時單鏈表的長度:"<<ListLength(L)<<endl;
int a=9;
LocateElem(L,a);
cout<<"該元素的位置爲:"<<LocateElem(L,a)<<endl;
int b=9;
LocateElem2(L,b);
cout<<"該元素的地址爲:"<<LocateElem2(L,b)<<endl;
GetElem(L,1,e);
cout<<"單鏈表L的第1個元素:"<<e<<endl;
ClearList(L);
cout<<"單鏈表L已清空"<<endl;
DestroyList(L);
cout<<"單鏈表L已銷燬"<<endl;
return 0;
}
//單鏈表的初始化,即構造一個空表
int InitList(LinkList &L)
{
L=new LNode; //或L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;
return OK;
}
//頭插法建立單鏈表
void CreatList(LinkList &L,int n)
{
for(int i=n;i>0;--i)
{
LinkList p;
p=new LNode;
cin>>p->data;
p->next=L->next;
L->next=p;
}
}
//尾插法建立單鏈表
void CreatList2(LinkList &L,int n)
{
LinkList r;//尾指針
r=L;//尾指針指向頭結點
for(int i=0;i<n;++i)
{
LinkList p;
p=new LNode;
cin>>p->data;
p->next=NULL;
r->next=p;
r=p;
}
}
//判斷鏈表是否爲空 ,若L爲空表,則返回1,否則返回0
bool ListEmpty(LinkList L)
{
if(L->next) //非空
return 0;
else
return 1;
}
//銷燬單鏈表L
int DestroyList(LinkList &L)//(從頭指針開始,依次釋放所有結點)
{
LinkList p;//或LNode *p;
while(L)
{
p=L;
L=L->next;
delete p;
}
return OK;
}
//清空單鏈表L (鏈表依然存在,但無元素,頭指針和頭結點仍然存在)
int ClearList(LinkList &L)//(依次釋放所有結點,並將頭結點指針域設置爲空)
{
LNode *p,*q;
p=L->next;//從首元結點開始
while(p)
{
q=p->next;
delete p;
p=q;
}
L->next=NULL;
return OK;
}
//在單鏈表L中第i個元素之前插入新元素e
int ListInsert(LinkList &L,int i,int e)
{
LinkList p;
p=L;
int j=0;
while(p&&j<i-1)
{
p=p->next;
++j;
}
if(!p||j>i-1) return ERROR;
LinkList s;
s=new LNode;
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}
// 刪除L中第i個數據元素,並用e返回其值
int ListDelete(LinkList &L,int i,int &e)
{
LinkList p,q;
p=L;
int j=0;
while(p->next&&j<i-1) //尋找第i個結點,並令p指向其前驅
{
p=p->next;
++j;
}
if(!p->next||j>i-1) return ERROR;
q=p->next;//保存被刪結點的地址以備釋放
p->next=q->next;//改變被刪節點前驅結點的指針域
e=q->data;
delete q;
return OK;
}
//求單鏈表的表長
int ListLength(LinkList L)
{
LinkList p;
p=L->next;
int i=0;
while(p)
{
i++;
p=p->next;//遍歷單鏈表,統計結點數
}
return i;
}
//L中查找與給定值e相等的元素,若成功,則返回該元素在表中的序號,否則返回0
int LocateElem(LinkList L,int e)
{
LinkList p;
int j=1;
p=L->next;
while(p&&p->data!=e)//循環結束條件:p爲空時或找到時
{
p=p->next;
j++;
}
if(p) return j;//若找到了,返回其位置
else
return 0;//若沒找到,返回0
}
//L中查找與給定值e相等的元素,若成功,則返回該元素的地址,否則返回NULL
LNode *LocateElem2(LinkList L,int e)
{
LinkList p;
p=L->next;
while(p&&p->data!=e)//循環結束條件:p爲空時或找到時
{
p=p->next;
}
return p;//若找到了,則直接返回地址,若沒找到,則此時p爲空了已經,返回空就行
}
//將單鏈表L中第i個位置的元素返回給e
int GetElem(LinkList L,int i,int &e)
{
LinkList p;
p=L->next;
int j=1;
while(p&&j<i)//p不爲空,j還沒到i
{
p=p->next;
++j;
}
if(!p||j>i) return ERROR;//沒有找到,元素不存在
e=p->data;
return OK;
}
運行結果:
算法時間複雜度分析:
雙向鏈表:
定義:
雙向鏈表的操作以及實現:(除插入和刪除操作,其他的操作與上述單鏈表的操作基本一致)
雙向鏈表的插入:
s->prior=p->prior //將p的前驅結點地址存到s的前指針域
p->prior->next=s //將p的前驅結點的指針域指向s
s->next=p //給s的next賦值p
p->prior=s //將p的前指針域指向s
雙向鏈表的刪除:
p->prior->next=p->next //讓指針變量p的前驅結點的指針域指向其後繼結點
p->next->prior=p->prior //將指針變量p的前驅結點的地址存到其後繼結點的前指針域
循壞鏈表:
定義:
循壞鏈表的合併:
帶尾指針循壞鏈表的合併的操作:
單鏈表,循壞鏈表和雙向鏈表的時間效率的比較:
順序表和鏈表的比較:
線性表的合併:
有序表合併--用順序表實現:
有序表的合併--用鏈表實現: