2.1線性表的定義和特點
【類型定義:
*是n個元素的有限序列
*除了第一個元素沒有直接前驅和最后一個沒有直接后驅之外,其余的每個元素只有一個直接前驅和直接后驅;
(a1,a2…an)
【特征:
*有窮性:由有限個元素組成,元素個數表長度 n=0空表
(a1,a2…an),稱下標i為線性表的位序
*有序性: 線性表元素之間存在嚴格次序關系(序偶關系)
*同一性:線性表屬于同類數據元素組成,每一個元素都屬于同一數據對象
eg:(A,12,b)不是線性表,不遵守同一性
2.1.2線性表抽象數據類型定義
ADT List{
//數據對象
D={ai|ai∈ElemSet,i=1,2…n, n>0}
//數據關系
R1={<ai-1,ai>|ai-1,ai∈D,i=2,…n}
//基本操作
1)InitList(&L)
將:初數化為空表
2)
…
}ADT list
【引用在什么情況下使用】
所用到的元素,哪個元素變化(帶入數據或者 ),就在此元素前加 &
【eg】
1)InitList(&L)
將:初數化為空表
//表L初始化帶入值,發生變化,故早L前加&
2.1.5 示例 *有序集合的合并
有序表:LA和LB,求合并遞減有序LC
基本思路:
{若ai<=bi,則ci=bi
{
{若bi<ai,則ci=ai
//誰小就先吧他賦給c
算法時間復雜度:O(ListLength(LA))+O(ListLength(LB))
2.2.1線性表的順序表示和實現------順序映像
【順序存儲】在【查找時】的時間復雜度為【O(1)】,因為它的地址是連續的,只要知道首元素的地址,根據下標可以很快找到指定位置的元素
【插入和刪除】操作由于可能要在插入前或刪除后對元素進行移動,所以順序存儲的時間復雜度為【O(n)】。
1)初始化操作
思想:構造一個空表
設置表起始位置、表長及可用空間
#define LIST_INIT_SIZE 100
#define LISTINCREAMENT 10
typedef struct
{
ElemType *elem; //定義個地址變量,使其后面能指向線性表占用的數組空間
int length; // 線性表的長度
int listsize; // 當前分配的存儲容量
}SqList;//初始化操作
Status InitList_Sq(SqList &L) //初始化
{ //構造一個空表
L.elem=(ElemType)malloc(LIST_INIT_SIZE*size of(ElemType));
if(!L.elem) exit(OVERFLOW); //存儲分配失敗
L.length=0; //空表長度為0
L.listsize=LIST_INIT_SIZE; //初始存儲容量
return OK;}
2.2.3順序表的插入
2)順序插入操作
目的:在線性表L第i個元素前插入一個元素e
【基本思想
1)判斷i是否在允許范圍
2)存儲空間是否已滿
3)將第i個元素和后面的所有元素向后移動
4)新元素寫在空出的第i個位置
5)線性表長度加1
【注意】
長度為n的順序表第i個位置插入移動n-i+1個元素
Status ListInsert_Sq(SqList&L,int i,ElemType e){if(i<1||i>1.lenth+1)
return ERROR; //插入位置不合法if(L.length>=L.listsize)
{
newbase=(ElemType*)realloc(L.elem,(LIST_INIT_SIZE+LISTINCREMENT)*sizeof(ElemType)); If(!newbase) exit(OVERFLOW);// 當前存儲空間已滿L.elem=newbase; //新基址L.listsize+=LISTINCREMENT; // 增加存儲容量
} //判斷空間足夠q=&(L.elem[i-1]); //q指向插入位置for(p=&(L.elem[L.elem-1]);p>-q;--q)
*(p+1)=*p;
*q=e;
++L.length;
return OK;
}
2.2.4順序表的刪除和插入
【基本思路
1)判斷i是否在允許范圍
2)將線性表的第i個元素給e
3)將第i個元素和后面的所有元素向前移動一個位置
4)線性表長度減1
Status ListDelete_Sq(SqList&L,int i,ElemType e){//刪除第i個元素并用e返回值if(i<1||i>1.lenth+1)
return ERROR; //刪除位置不合法p=&(L.elem[i-1]); //q指向插入位置
e=*p;
q=L.elem+L.length-1; //表尾元素位置
for(++p;p<=q;++p)
*(p-1)=*p; //p-1指向p--L.length;
return OK;
}
【圖】:平均移動次數
【查找操作】11:42
int LocateElem_Sq(SqList,ElseType e)
{
//查詢第一個 滿足條件的元素,若存在,返回位序,否則返回0;
int i;
i=1;
while (i<=L.length&&L.elem[i-1]!=e)
++i;
if(i<=L.length)
return i;
else return 0;
}// locateElem_Sqi<=L.length&&L.elem[i-1]!=e
i>L.length
【順序結構優缺點14:15
【優點:
邏輯相鄰,物理相鄰
可隨機存取一元素
存儲空間使用緊湊
【缺點:
插入,刪除需要移動大量元素
預先分配空間需按最大空間分配,利用不充分表難以擴充
【】線性表的合并問題
【圖:例1】
基本思路:
1)初始化Lc為空表
2)分別從La和Lb取得當前元素ai和bi
3)若ai<bj,則將ai插入到Lc中,否則
bj插入到Lc中
代碼:
Viod MergeList(SqList La.SqList ,lb.SqList &Lc)
{
Pa=La.elem;
Pb=Lb.elem;
Lc.listsize=Lc.length=La.elem+Lb.elem;
Pc=Lc.elem=(ElemType*)malloc(Lc.listsize* sizeof(ElemType));
if(!Lc.elem)
exit(overflow);
Pa_last=La.elem+La.length-1;
Pb_last=Lb.elem+Lb.length-1;
while(pa<=pa_last&&pb<=pb_last)
{
if(*pa<= *pb)
*pc++= *pa++;
else
*pc++= *pb++;
}
while(pa<=pa_last)
*pc++= *pa++;
while (pb<=pb_last)
*pc++= *pb++;
}
2.3線性表的鏈式表現與實現
2.3.1.1單鏈表
【特點:
*用一組任意的存儲單元存儲線性表的數據元素
*利用指針實現用不同相鄰的存儲單元存放邏輯上相鄰的元素
*每個元素ai,除存儲本身信息外,還存儲其直接后繼的元素(后一個元素的地址)
*結點:數據元素ai的存儲映像
{數據域:數據元素本身
指針域:指示直接后繼的存儲位置
【頭指針、頭結點、第一個元素結點
*頭指針:以線性表的第一個數據元素a1的存數地址作為線性表的地址,稱為線性表的頭指針
*頭結點:為了操作方便,在第一個結點前虛加一個“頭結點”,指向頭結點的指針為鏈表的頭指針(相當于第一個呀元素的結點)
代碼:
typedef struct LNode{ElemType data;struct LNode*next;
}LNode,*LinkList //LNode是結構體的別名,LinkList為指針變量
//相當于:typedef LNode *LinkList
2.3.1.2 單鏈表存儲結構實現
格式: data | next
【p指向數據域
(*p).data=10;
或:p->data=10; //表示p指向結點的數據域
(*p).next=10
或 p->next //表示p指向結點的指針域
*生成一個LNode型新結點:
p=(LinkList)malloc(sizeof(LNode));
*系統回收p的結點
free(p)
*單鏈表特點:
1)是它是一種動態結構,整個存儲空間為多個鏈表共用
2)不需預先分配空間
3)指針占用額外存儲空間
4)不能隨機存取,查找速度慢
【基本操作:
1)GetElem(L,i,&e) //第i個元素用e帶回結果
2)ListInsert(&L,i,e) //插入
3)ListDelete(&L,i,e) //刪除
4)CreateList_L(&L,n) //創建線性表
2.3.1.3單鏈表的查找
【操作:
1)GetElem(L,i,&e)
【基本思想:
1)令p為指針變量,首先指向第一個結點,變量 j為計數器
2)依次向后查找,循環結束條件:p為空或j>=i;
3)找到用e返回第i個值
【代碼:
Status GetElem_L(LinkList L,int i,ElemType&e)
{ //L是鏈表的頭指針(對帶頭結點的鏈表),以e返回dii個值
p=L->next;
j=1;while (p&&j<i)
{p=p->next; ++j;if(!p||j>i)return ERROR;e=p->data; //取第i個值
return OK;
}
2.3.1.4單鏈表的插入操作
2)ListInsert(&L,i,e)
在線性表第i個元素之前插入一個元素e,元素e存在結點s中
【思路:在第i項的前加一個接結點,i-1項的地址域和e的數據域連接
int ListInsert_L(LinkList&L,int i,int e)
{
LNode*p,*s;int j; //或:LinkList p,s; 等同
p=L;j=0; //計數器
while(p&&j<i-1)
{p=p->next;++j;}
if(!p||j>i-1)
return ERROR;
s=LinkList()malloc(sizeof(LNode)); //新結點
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}
2.3.1.5 單鏈表的刪除
【思路:刪除第i個元素,并保存到元素e中
【代碼:
int ListDelete_L(LinkList&L,int i,ElemType&e)
{
LNode*p,*q;int j;
p=L;j=0;
while(p->next||j<i-1) //????我覺得應該是(!p||j>i-1)
{p=p->next;++j}
if(p->next==NULL||j>i-1)
return ERROR; //刪除位置不合理
q=p->next; //q指向被刪除結點
p->next=q->next; //
e=q->data; //取出第i個結點的數據域
free(q); // 釋放dii個結點的內存
return OK;
}
2.3.1.6單鏈表的建立
【頭插法建立有頭結點的單鏈表
可理解為:每次插一個新的頭
【圖】
L=(Linklist)malloc(sizeof(LNode)) //sizeof后面跟數據類型(LNode)
L->next=NULL
【圖】
p=(LinkList)malloc(sizeof(LNode))
scanf("%f",&(p->data)); //
【整個代碼:
void CreateList_L(LinkList &L,int n)
{
LNode*p;int i;
L=(LinkList)malloc(sizeof(Lnode));
L->next=NULL;
for(i=n;i>0;--i)
{ p=(Listlink)malloc(sizeof(LNode));
scanf("%d",&p->data);
p->next=L->next;
l->next=p //這里的=都可以理解為“給了,到,指向”
}
}
2.3.1.7有序單鏈表的合并
例:線性表LA和LB中數據元素按照廢帝劍有序排列,將LA和LB合并為一個新的LC,且LC中的數據元素仍按照遞減有序排列
【代碼:
void MergeList_L(LinkList&La,LinkList&Lb,LinkList&Lc)
{// 歸并La和Lb得到Lc,Lc也按照降序排列
LinkList pa,pb,pc;
pa=La->next;pb=Lb->next;
Lc=pc=La; //用La的頭結點作為Lc的頭結點
while(pa&&pb)
{
if(pa->data<=pb->data)
{
pc->next=pa; pc=pa;pa=pa->next;
}
else
{
pc->next=pb;pc=pb;pb=pb->next;
}
pc->next=pa?pa:pb; //若a不為空則指向pa,否則指向pb
free(Lb);
}
2.3.1.8靜態鏈表
定義:用數組描述的鏈表叫靜態鏈表
目的:為在不設指針類型的高級程序語言中使用鏈表結構
存儲結構:
#define MAXSIZE 100 //靜態鏈表最大長度
typedef struct{
ElemType data;
int cur; //游標,代替指針的結點,表示數組中的位置
}component,SLinkList[MAXSIZE]
2.3.2循環鏈表
循環鏈表是單鏈表的變形
循環鏈表最后一個結點link指針部位NULL,而是指向表的前端
為簡化操作,在循環鏈表往往插入頭結點
特點:
只要知道表中一結點的地址,就可以搜索到所有其他結點的地址
操作的時間復雜度:
表尾插入,時間復雜度:O(1)
表尾刪除:O(n)
表頭插入,同表尾
表頭刪除:O(1)
2.3.3雙向鏈表 插入、刪除
指在前驅和后驅方向都能游歷(遍歷)的線性鏈表
雙向鏈表的每個結點有兩個指針域
【結構】:prior data next
雙鏈表通常采用帶頭結點的循環鏈表形式
可理解為首位相接的數據“圈”,每個結點都可以向前或向后走
【結點指向】
【插入操作】:
1.分配空間
2.斷開與連接
【操作算法
status ListInsert_DuL(DuLinkList &L,int i,ElemType e)
{if(!p=GetElem_Dul(L,i))
return ERROR; //相當于嵌套第i個結點的指針if(!(s=(DuLinkList)malloc(sizeof(DuLNode))))
return ERROR //空間分配失敗
s-data=e; //將數據放入新結點的數據圖
s-prior=p-prior; //將p的前驅結點指針放入新結點的前向指針域
s-next=p; //將p放入新結點的反向指針域
p-prior-next=s; //修改p的前驅結點的反向指針
p-prior=s; // 修改p的前驅指針
return OK;
} ListInsert_DuL
【刪除操作】
1.p指向目標結點
2.將目標結點的前一個結點與后一個連接(跳過中間那個)
3釋放內存
【操作算法】
status ListDelete_Dul(DuLinkList &L,int i,ElemType &e)
{ 刪除頭結點的雙向循環鏈表L中第i個元素返回,1=i=表長
if(!p=GetElem_Dul(L,i))
return ERROR; 查找第i個指針
e=p-data; 將p指向結點數據域中的值取出
p-prior-next=p-next; p前一個結點的后驅指向p的后一個結點
p-next-prior=p-prior; 后指向前
free§; 釋放p
return OK;
} ListDelete_DuL
【 算法評價:T(n)=O(n) 】
!注意:如何選擇合適的存儲結構
鏈表只能順序存取,在單鏈表的最后一個元素后插入元素,需遍歷整個鏈表
頻繁插入刪除用鏈式存儲
偶爾 用順序存儲
2.4一元多項式的表示及相加
- n階多項式的表示:
n階多項式有n+1項
指數按升冪排序
【 優點:
- 多項式的項數可以動態增長,不存在存儲溢出的問題
- 插入,刪除方便,不移動元素
【表示:
有兩個數據域,一個地址域
【一元多項式的建立算法:
void polycreate(Polylist &head)
{
polylist rear,s; int c,e;
head=(Polynode *)malloc(sizeof(POlynode));
rear=head; //尾插法
scanf("%d,%d",&c,&e);
while(c!=0){ s=(Polynode *)malloc(sizeof(Polynode));s->coef=c; s->exp=e;rear->next=s; rear=s;scanf("%d,%d",&c,&e);rear->next=NULL;}
}
【一元多項式相加:
-
掃描兩個多項式
{若當前被檢測項指數相等,系數相加。 和不為0,則結果加到結果多項式{若檢查指數不等,將指數小的加到結果多項式,然后往后移
-
若一個多項式檢測完,將另一個多項式剩余全部復制到結果多項式
【設計思想:
算法:
void polyadd(Polylist polya,Polylist polyb)
{
Polynode *pa,*pb,*pc,*r;
int sum;
pa=polya->next;
pb=polyb->next;
pc=polyb; //pre指向和多項式的尾結點while(pa!=NULL&&pb!=NULL)
{
if(pa->exp<pb->exp){pa->next=pa; pc=pa;pa=pa->next;} //【1.】 pa指數小于pb指數,把pa給了pc
else if(pa->exp==pb->exp){sum=pa->coef+pb->cofe;if(sum!=0) //【3.】指數相等,但系數sum不是0{pa->cofe=sum; pc->next=pa;pc=pa; pa=pa->next;r=pb; pb=pb->next ; free(r);}else //【4.】指數相等,系數sum為0{r=pa; pa=pa->next;free(r);r=pb;pb=pb->next;free(r);}}else{pc->next=pb; pc=pb;pb=pb->next;} // 【2.】pb指數小于pa指數,把pb給了pcif(pa!=NULL)pc->next=pa;
else pc->next=pb;
}}