什麼是線形表? <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
答:一種數據結構,簡單的說, N 個數據元素的有限序列。
線性結構的特點:
① 只有一個首結點和尾結點;
② 除首尾結點外,其他結點只有一個直接前驅和一個直接後繼。
線性表順序存儲特點:
① 邏輯上相鄰的數據元素,其物理上也相鄰;
② 若已知表中首元素在存儲器中的位置,則其他元素存放位置亦可求出(利用數組下標)。
例 1 :一個一維數組M,下標的範圍是0到9,每個數組元素用相鄰的5個字節存儲。存儲器按字節編址,設存儲數組元素 M[ 0]的第一個字節的地址是98,則M [ 3 ] 的第一個字節的地是 ______
解: 地址計算通式爲
LOC(ai) = LOC(a1) + L * ( i-1 )
因此: LOC( M[3] ) = 98 + 5 × (3-0) =113
線形表的實現:
定義該數據結構:
#define LIST_INIT_SIZE 100 // 初始化分配量, 100 個元素爲默認的 線性表的容量
#define LISTINCREMENT 10 // 線性表存儲空間的分配增量
Typedef struct {
Elemtype *elem ; // 定義一個指針,用於存放線性表的基址
Int length ; // 定義線性表的長度
Int listsize ;}sqlist // 當前線性表分配到的容量
創建一個線性表:即初始化一個線性表,構造一個空的線性表
Status initList_sq (sqlist &L) {
L.elem = (ElemType *) malloc (LIST_INIT_SIZE*sizeof(ElemType))
If (!L.elem) exit(OVERFOLOW);
L.lengh = 0 ;
L.listsize = LIST_INIT_SIZE ;
Return OK }
順序表的插入操作(重要,必須倒背如流)
思路:
① 插入操作應該必須知道些什麼呢?
答:插到哪個線性表,和插到該線性表的什麼位置,還有插的元素,所以該插入函數有三個參數 &L , int I ,elemtype e .
② 插入操作中會有什麼可能出現的錯誤呢?
答 A: 插入的位置應該是邏輯上成立的 1 =< I <= length(L)+1]
B: 如果線性表已經裝滿了,應該分配新的空間
Length>=listsize 時候說明滿了
Newbase = (elemtype *) realloc (L.elem,
( L.listsize + LISTINCREMENT ) *sizeof(elemtype));
③ 插入後,會對別的元素造成什麼樣的影響?
答 如果插入的位置是 I ,那麼 從 I ->length(L) 都將被移動,共需移動元素 N-I+1 很明顯,應該從最右邊的元素下手,依次將 I -> n 的元素右移。 P 是指向最後一個元素的指針, *(P +1)= *P , 最後應該完成掃尾工作 ++length .
下面寫出原碼 :
Void List_insert_sq(&L , int I ,elemtype e .) {
if (i<1 || i>length(L)+1); return Error; // 插入的位置應該是邏輯上成立
if (L.length >= L.listsize ) { // 如果線性表已經裝滿了,應該分配新的空間
Newbase = (elemtype *) realloc (L.elem,
(L .listsize + LISTINCREMENT ) *sizeof(elemtype));
If (!newbase = 0 ) exit(OVERFLOW)
L.elem = Newbase;
L.Listsize = .listsize + LISTINCREMENT ; }
Q = &(L.elem[i-1]); // 插入的位置
For (P = &(L.elem[L.length-1]) ; P>=Q; P--) {
*(P +1)= *P ; } // 依次將 I -> n 的元素右移
*P = e ;
++L.Length ; }
分析上程序的時間複雜度
最壞的情況下是: 插入的位置是 1 ,將移動 N-I+1 個元素,插入的位置的概率是 1/n+1, 所以平均算法時間複雜讀是 O(N/2) ;
順序表的刪除操作(重要,必須倒背如流)
思路:
1 刪除操作應該必須知道些什麼呢?
答:刪除哪個線性表,和刪除該線性表的什麼位置,返回刪除元素的值,所以該插入函數有三個參數 &L , int I ,elemtype e .
2 刪除操作會出現什麼可能的錯誤?
答:刪除的元素應該在線性表內 1=< I <= length
3 刪除後會對周圍元素造成什麼樣的影響?
答:刪除的位置如果是 I ,那麼 i+1 到 N 的元素都將會左移一位 * ( P-1 ) =*P
很明顯,應該把從線性表的左邊移動,此前把 *P 賦給 e , 最後掃尾 –Length
下面給出原碼:
Status list_delete_sq (&L , int I ,elemtype e .) {
If (i<1 || i>length(L)) then return error ;
p = L.elem[i-1] ;
E = *p;
Q = L.elem + L.length-1;
For (p; p<=q ; p++)
*( P-1 )=* P ;
---L.length ; }
分析上程序的時間複雜度
最壞的情況下是: 刪除的位置是 1 ,將移動 N-I 個元素,插入的位置的概率是 1/n+1, 所以平均算法時間複雜讀是 O((N-1)/2) ;
關於 MALLOC 與 REALLOC 的理解
首先看一下下面的 C 程序片斷:
Elemtype malloc(size) // Elemtype 指的是數據類型,比如 INT ,那就意味着一個指針指示 4 個字節
Elemtype realloc( 原指針, SIZE) ,即一個元素是 4 個字節。
#include <malloc.h>
char *p;
p = (char * ) malloc (10);
p = (char * ) realloc (p,20);
函數首先定義了一個字符型的指針 p ,然後爲指針 p 分配了一個 10 個字節大小的內存空間,接着將這個內存塊的大小增加到 20 個字節。關於 realloc 究竟在哪裏分配的問題,完全由實現標準庫的程序決定。它可能在原來的地方擴充,也有可能全部另行分配(想想,如果現在要擴大存儲塊,而後面的地方已經被分配出去另有他用了,你怎麼可能在原位擴充呢?)。具體情況我們不必考慮,從抽象的層次上理解其功能就夠了。
順序表的定位操作:(巧妙的設計)
思路:要求查找線性表中是否有與 e 相同的元素,如果有則返回該元素的序號,否則返回 0
1. 定位過程必須知道什麼?
答: 對哪個表進行定位,返回與哪個數相等的序列號。所以需要兩個參數
2. 怎麼進行比較控制?
答: 須對線性表中的元素都進行比較,所以應該知道線性表的長度,用一個循環依次取出每個元素比較。找到回返回序號。否則返回 0
Int locate_list-sq (&L,e) {
For (I = 1 ; i<=L.length&&!(e==L.elem[i-1]);++i) {}
If (I<=L.length) then reture I
Else return 0 ; }
該算法的巧妙之處在於 巧妙的利用了 ++i,意思是在 L 查找不到相等的元素後跳出循環, I 已經先加上 1 了,所以 I>L.length, 返回的值是 0 。
分析上程序的時間複雜度
最壞的情況下是:找到符合條件的元素在最後一個,所以時間複雜度是 O ( L.Length )
線性表之間的操作:
例 1 :線性表 LA 與 LB ,求 A=AUB ;
思路:求兩線性表的並,注意集合的定義,沒有相同的元素!,一個循環解決問題
Void union(LA,LB) {
For (I = 1, i<=LB.length;i++){
Getelem(LB,I,e);
If (!locateelem(LA,e));
Listinsert(LA,++LA.length+1,e) }
例 2 :線性表 LA 與 LB 中元素是按非遞減有序排列,求 LC=LAULB ;
思路:初始化一個線性表 LC ,將 LA 與 LB 中元素比較,小的進 LC 。最後再掃尾
Void mergelist(LA,LB,LC) {
Init(LC);
I=j=k=1 ;
While (i<=la.length && j<=lb.length)
{ Getelem(la,I,ai) ; Getelem(la,j,ai) ;}
If (ai<=bi) {listinsert(LC, k++,ai) ; i++}
Else {listinsert(LC,++k++,bi) ; J++}
While(i<=la.length) {getelem(la,i++,ai) ;listinsert(LC, k++,ai);
While(j<=lb.length) {getelem(lb,j++,bi) ;listinsert(LC, k++,bi);
線性表的鏈式表示和實現
鏈式存儲特點:
答: 邏輯上相鄰,物理上不一定相鄰
在單鏈表中,除了首元結點外,任一結點的存儲位置由其直接前驅結點的鏈域的值指示
頭結點 是在鏈表的首元結點之前附設的一個結點;數據域內只放空表標誌和表長等信息 ;
首元結點 是指鏈表中存儲線性表第一個數據元素 a1 的結點。
單鏈表是非隨機訪問的儲存結構,它的 I 結點的存儲位置包含在 i-1 個結點的信息中。
對單鏈表的訪問操作 GETELEM
怎麼訪問 LINKLIST 中任一指定的結點?
答:與順序表不同,順序表只需要知道線性表的基址,再加上偏移量就可以得出。而鏈表的物理存儲位置可能是不相鄰的,因此我們必須從頭結點開始,纔可以找出某結點的存儲位置。
那怎麼控制呢?
答:如果訪問的是第 5 個元素,那我們只需要找到 5-1=4 個元素,就可以訪問它了。我們可以設置一個計數器控制循環,一直訪問到 i-1 個元素。
怎麼控制查詢範圍的錯誤呢?
答:如果 i-1 個元素的指針域爲空,那就說明 i-1 是鏈表的最後一個結點,沒有 I 結點,返回錯誤。如果查詢的是 <1 的結點,返回錯誤。
下面給出算法:
Status Getelem(linklist L, int I, elemtype e) }
P=L->next ; j=1;
While ( p && j < I ) {
P = p->next ;
++J }
If ( !p && j > i) returnerror ;
E = p->data ;
Return OK; }
單鏈表的插入操作:
思路: 在順序鏈表中,在 I 前插入一個元素,只需要將 N-I+1 個元素都向右移動一位首先將指針移動到表尾元素 *(p+1)=*p . 但是鏈表在物理上的存儲空間是不相鄰的,第 I 個元素的物理位置是保存在第 I-1 個元素的指針域裏,所以,首先,我們必須找到第 I-1 個元素,也就是我們需要得到第 I-1 個元素的指針。
怎麼能夠得到第 I-1 個元素的指針呢?
答: L 是頭指針,指向頭結點。如果我們想得到指向 I-1 個元素的指針,我們應該將 L 移動 I - 1 次。所以應該注意,循環 I-1 次 。
如果我們想要插入的位置不在 1 <=i<=n+1 呢?
答:如果插入的位置超出表長,怎麼判斷超出了表長?如果一個鏈表有 6 個元素,那麼從 1~7 的插入位置都是合理的,如果插入的位置是 7 ,那麼 I-1 個元素的既 6 的指針是存放在第 5 個元素裏的。所以 I-1 元素的指針不爲空。那麼如果剛剛超出表長一位,要求插入的位置是 8 ,那麼 I-1 個元素即 7 的指針是存放在 6 裏面的,而表長是 6 ,所以 6 的指針爲空。 由此我們得出,看是否超出表長,只需要判斷 I-1 個元素的指針是否爲空。其次,插入位置當然不能小於 1 。
下面是源碼:
Status listinsert_link (linklist L, int I , elemtype e) {
P = L ; j = 0 ;
While( p && j < i-1 ) {p=p->next , ++j}
If (!P && j > i-1) returnerror ;
S = (linklist) malloc (sizeof(Lnode)) ;
s->next = p-.next ;
p-next = s ;
s->data = e ;
return OK ; }