查找操作
算法思想
查找運算可采用順數查找,即從第一個元素開始,依次將表中的的元素與所要查找的元素進行比較,如果相等,則查找成功。如果查找成功輸出相應的提示信息,反之也給予相應的提示信息。
算法實現
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
typedef struct{
int *elem;
int length;
int listsize;
}SqList;
void CreatList(SqList &L)
{//建立一個線性表
L.elem=(int *)malloc(MAX * sizeof(int)); //動態數組空間分配
if(!L.elem) //! 是“非”的意思,如果沒被分配空間 就結束程序。
return;//exit(0)
L.listsize=MAX;
printf("輸入表的長度:");
scanf("%d",&L.length);
printf("輸入%d個數:",L.length);
for(int i=0;i<L.length;i++)
scanf("%d",&L.elem[i]);
}
void Traverse(SqList L){
//遍歷
printf("表中數據為:");
for(int i=0;i<L.length;i++)
printf("%3d",L.elem[i]);
printf("\n");
}
void LocateElem(SqList L,int e){
//查找
int i;
printf("輸入查找的元素:");
scanf("%d",&e);
for(i=0;i<L.length;i++)
{
if(L.elem[i]==e){
printf("查找成功,查找元素為%d",L.elem[i]);
printf("\n");
return;// =return 0;相當於是退出程序
}
}
printf("查找失敗");
printf("\n");
}
int main(void)
{
SqList L;
CreatList(L);
Traverse(L);
LocateElem(L,1);
return 0;
}
運行演示
算法小結
- 首先要先創建一個線性表。
- 第二就是對線性表進行遍歷,查找后輸出。
L.elem=(int *)malloc(MAX * sizeof(int));
動態數組空間分配, 因為要靜態的數組的空間只要定義好了之后就不能再被分配了。我們這里是數組的長度是我們輸入來實現的,所以是動態的。(MAX * sizeof(int))
這劇代碼返回的是整數型的數據,所以指針用的是int *
定義指針 的原因是因為malloc
函數只能返回第一個字節的地址,但是這個地址是沒有含義的,為什么說是沒有意義的呢?因為只知道這個地址,但是后面有幾個字節是不知道。void CreatList(SqList &L)
&
代表引用,使用&
后所傳參數會改變,仔細觀察你會發現,凡是對鏈表有改動的,如刪除函數,增加函數,形參前面都會有&符號,那是因為調用了這個函數后,所傳鏈表(結構體)會改變。
插入操作
算法思想
查找運算可采用順數查找,即從第一個元素開始,依次將表中的的元素與所要查找的元素進行比較,如果相等,則查找成功。如果查找成功輸出相應的提示信息,反之也給予相應的提示信息。
算法實現
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
typedef struct{
int *elem;
int length;
int listsize;
}SqList;
void CreatList(SqList &L)
{//建立一個線性表
L.elem=(int*)malloc(MAX *sizeof(int));
if(!L.elem)
return;//exit(0)
L.listsize=MAX;
printf("輸入表的長度:");
scanf("%d",&L.length);
printf("輸入%d個數:",L.length);
for(int i=0;i<L.length;i++)
scanf("%d",&L.elem[i]);
}
void Traverse(SqList L){
//遍歷
printf("表中數據為:");
for(int i=0;i<L.length;i++)
printf("%3d",L.elem[i]);
printf("\n");
}
void ListInsert(SqList &L) //改變鏈表中元素,有&符號
{//插入元素及其要插入的位置
int i;
int e;
printf("輸入要插入位置及元素\n");
scanf("%d%d",&i,&e);
printf("在順序線性表中第%d個位置之前插入新的元素%d。\n",i,e);//在順序線性表L中第i個位置之前插入新的元素e,
if(i<1||i>L.length+1) return; //i的合法位置為1<=i<=ListLength(L)+1
int *p,*q;
q=&(L.elem[i-1]);
for(p=&(L.elem[L.length-1]);p>=q;--p)*(p+1)=*p;
*q=e;
++L.length;
return;
}
int main(){
SqList L;
CreatList(L);
Traverse(L);
ListInsert(L);
Traverse(L);
return 0;
}
運行演示
算法小結
q=&(L.elem[i-1]);
表示從鏈表的第i個元素開始一直到最后一個元素往后移一位.p=&L.elem[L.length-1]
意思是p賦初值為鏈表的最后一個元素地址,p>=q表示循環知道p<q的時候結束,--p是使p指針的指向往前移一位.
刪除操作
算法思想
用順序表作為線性表的存儲結構時,由於結點的物理順序必須和結點的邏輯順序保持一致,因此當需要刪除第i個元素時,必須將表中位置相似i+1,i+2,...,n-1,n上的節點,依次前移到位置i,i+1,...n-1(其中n為L的表長度)。
算法實現
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
typedef struct{
int *elem;
int length;
int listsize;
}SqList;
void CreatList(SqList &L)//對鏈表有改動 形參有&符號
{//建立一個線性表
L.elem=(int*)malloc(MAX *sizeof(int));
if(!L.elem)
return;//exit(0)
L.listsize=MAX;
printf("輸入表的長度:");
scanf("%d",&L.length);
printf("輸入%d個數:",L.length);
for(int i=0;i<L.length;i++)
scanf("%d",&L.elem[i]);
}
void Traverse(SqList L){
//遍歷
printf("表中數據為:");
for(int i=0;i<L.length;i++)
printf("%3d",L.elem[i]);
printf("\n");
}
int makesureElem(SqList L,int e)//這里需要用到兩個參數,首先是 鏈表,另外一個就是刪除的元素(表中)
{
int i;
//確定要刪除的元素
for(i=0;i<L.length;i++)
{
if(L.elem[i]==e)
{
printf("要刪除的元素 位置為 %d",i+1);
printf("\n");
return (i+1);
}
}
printf("元素不存在");
printf("\n"); return 0;
}
int ListDelete(SqList &L){ //對鏈表有改動 形參有&符號
//刪除元素
int i;
int e;
printf("輸入要刪除的元素");
scanf("%d",&e);
i=makesureElem(L,e);
if((i<1)||(i>L.length)) return 0;//i的合法值為1<=i<=ListLength(L)+1
else{
int* p,* q;
p=&(L.elem[i-1]);//取回鏈表中每一個元素的地址賦值給p,則指針p指向的內容就是*p
e=*p;//找到表中要被刪除的元素“e”(也就是*p)
q=L.elem+L.length-1;//尾元素的位置,更多解釋看說明
for(++p;p<=q;++p)//初始化指針並移動,更多解釋看說明
{
*(p-1)=*p; //被刪除元素之后的元素左移
}
--L.length;
printf("元素被刪除");
}
return 0;
}
int main(){
SqList L;
CreatList(L);
Traverse(L);
ListDelete(L);
Traverse(L);
return 0;
}
運行演示
算法小結
-
q=L.elem+L.length-1
順序存儲結構(實際上就是數組)中,l.elem
表示線性表l中存儲數據(數組)的基地址(起始地址)這個也就是一開始建立順序表的原因,其實本質上就是進行了用程序模擬了c語言中的數組,之所一開始int* elem
的原因就是 和 數組中的的定義一樣,例如數組a[2]
a
就是a[0]
的地址,即a == &a[0]
,l.length
是表的長度(數據元素個數),q
是指針通過上式計算后指向尾元素和數組的情況一樣,例如:int a[10],*p=a;
//p指向第一個元素
p=a+1;
//指向第二個元素.
例如:p=a+10-1;
指向最后一個數組元素,即a[9]
. -
for(++p;p<=q;++p)
p是一個被初始化過的指針,按上面代碼應該指向某類型的數組,為超表達方便,數組記為x(i)。for循環首先把p從當前位置x(k)移動到x(k+1)作為初值,只要指針沒到q指向的位置,就繼續循環,循環每次遞增一個數據。循環體將數組當前位置數據拷貝到前一個位置。總之,初始時,如果p指向x(m),q指向x(n),n應該大於m。最終運行結果是x(m)=x(m+1)=...=x(n)。 -
刪除操作中函數的返回值是
int
型的,當然也可以是void
的型的,如果是void
型的話,把return 0;
寫成return;
就好了。
順序表合並
有兩個順序表LA 和 LB,其元素均為非遞減有序排列,可設兩個指針i、j 分別指向表LA 和 LB 中的元素,若LA.elem[i]>LB.elem[j],則當前先將LB.elem[j]插入到表LC中,若LA.elem[i]<=LB.elem[j],則當前先將LA.elem[i]插入到表LC中,如此進行下去,其中一個表被掃描完畢,然后再將未掃描完的表中剩余的所有元素放到表LC中。其中個一個表被掃描完畢,然后再將未掃描完的表中剩余的所有元素放到表LC中。
算法演示
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
typedef struct{
int *elem;
int length;
int listsize;
}SqList;
void CreatList(SqList &L)
{//建立一個線性表
L.elem=(int*)malloc(MAX *sizeof(int));
if(!L.elem)
return;//exit(0)
L.listsize=MAX;
printf("輸入La表的長度:");
scanf("%d",&L.length);
printf("輸入%d個數:",L.length);
for(int i=0;i<L.length;i++)
scanf("%d",&L.elem[i]);
}
//創建第二個表
void CreatList2(SqList &L2)
{
L2.elem=(int*)malloc(MAX *sizeof(int));
if(!L2.elem)
return;//exit(0)
L2.listsize=MAX;
printf("輸入Lb表的長度:");
scanf("%d",&L2.length);
printf("輸入%d個數:",L2.length);
for(int i=0;i<L2.length;i++)
scanf("%d",&L2.elem[i]);
}
void Traverse(SqList L){
//遍歷
printf("La中數據為:");
for(int i=0;i<L.length;i++)
printf("%3d",L.elem[i]);
printf("\n");
}
void Traverse2(SqList L2){
//遍歷
printf("Lb表中數據為:");
for(int i=0;i<L2.length;i++)
printf("%3d",L2.elem[i]);
printf("\n");
}
void Traverse3(SqList L3){
//遍歷
printf("組合表Lc中數據為:");
for(int i=0;i<L3.length;i++)
printf("%3d",L3.elem[i]);
printf("\n");
}
void MergeList_Sq(SqList la,SqList lb,SqList &lc)//增加的是lc的操作,前面的幾個例子也說名了,只要牽扯到改變表中數據
//就用引用符號&
{
int* pa = la.elem; // 對順序表中第一個元素的地址進行初始化
int* pb = lb.elem;
lc.listsize = lc.length = la.length + lb.length; //Lc表初始化大小
int* pc = lc.elem = (int*)malloc(MAX *sizeof(int)); //請求系統進行動態內存分配
if (!lc.elem)
{
return;//存儲分配失敗
}
int* pa_last = la.elem + la.length - 1; //定義La 順序表中末尾元素的地址,而且在線性表中地址都是遞增,且遞增幅度不變
int* pb_last = lb.elem + lb.length - 1; //看下面的圖就清晰了
while (pa <= pa_last && pb <= pb_last) //其實都是比較的地址 ,還有就是注意<= 不要寫成<
{
if (*pa <= *pb)//對於指針指向的內容作比較
{
*pc++ = *pa++; // 等價於 *pc = *pa; *pa++; *pc++;注意此處的 *pa++ 不可以寫成* (++pa),說明看算法小結
}
else
{
*pc++ = *pb++;
}
}
while (pa <= pa_last) *pc++ = *pa++;//插入La的剩余元素
while (pb <= pb_last) *pc++ = *pb++;
}
int main(){
SqList L,L2,L3;
CreatList(L);
Traverse(L);
CreatList2(L2);
Traverse2(L2);
MergeList_Sq(L,L2,L3);
Traverse3(L3);
return 0;
}
運行演示
算法小結
int* pa_last = la.elem + la.length - 1;
這段代碼就是求表中最后一個元素的地址,而地址是按一定的幅度遞增的,如下圖
-
while (pa <= pa_last && pb <= pb_last)
這里面的符號,一定不要寫錯了,一開始就是因為<= 寫成了< 導致程序運行失敗,並沒有進行排序
就直接存到Lc中了。 -
*pc++ = *pa++;
等價於 *pc = *pa; pa++; pc++;注意此處的 pa++ 不可以寫成 (++pa),因為是先把pa 賦值給 pc ,所以不能寫反了,如果寫反了就是先p+1 ,然后才賦值給pc,這樣程序邏輯會出錯。
線性順序表優缺點
優點
1.無須為表示結點間的邏輯關系而增加額外的存儲空間(因為邏輯上相鄰的元素其存儲的物理位置也是相鄰的)。
2.可以方便的隨機存取表中的任一個元素。
缺點
1.插入和刪除運算不方便,除表尾的位置外,在表的其他位置上進行插入或者刪除操作都必須移動大量的結點,其效率低。
2.由於順序表要求占用連續的存儲空間,存儲分配只能預先進行靜態分配。因為當表變化較大時,難以確定合適的存儲規模。若按可能達到最大長度預先分配表空間,則可能造成一波分空間長期閑置而得不到充分利用;若事先對表長估計不足,則插入操作可能使表長超過預先分配的空間而造成溢出。
參考文獻
- 數據結構-用C語言描述(第二版)[耿國華]
- C語言數據結構之線性表的基本操作
- 數據結構(C語言版)[嚴蔚敏,吳偉民]