8.Python:深淺copy
線性表
定義
- 線性表是具有相同特徵的資料元素的一個有限序列
- a1,a2,a3·····,ai-1,ai,ai+1,······,an
- 上面元素稱為資料元素
- a1 稱為線性起點 an是線形終點,n為元素總個數,即表長
- ai-1是ai的直接前趨, ai+1是ai的直接後續
- 當n=0時,稱為空表
- 線性表:
- 由n(n>=0)個數據元素(節點)a1,a2,a3,a4····an組成的有限序列
- 其中資料元素的個數n定義為表的長度
- 當n= 0時稱為空表
- 當非空的線性表(n>0)記作:(a1,a2····an)
- 這裡的資料元素ai(1<=i<=n)只是一個抽象的符號,其具體含義在不同情況下可以不同
邏輯特徵
- 在非空線性表,有且僅有一個開始節點a1,他沒有直接前趨,而僅有一個直接後續a2
- 有且僅有一個終端節點an,他沒有直接後續,而且僅有一個直接前趨an-1
- 其餘的內部節點ai(2<=i<=n-1)都有且僅有一個直接前趨ai-1和一個直接後續ai+1
案例
-
一元多項式的計算
-
當表示式為p0 + p1x^1 + p2x^2 + p3x^3 + ····pn*x^n;
-
可以使用以為陣列來表示
Pn(x)
-
0 1 2 3 n p0 p1 p2 p3 pn Qn(x)
0 1 2 3 n q0 q1 q2 q3 qn 則線性表Rn(x) = Pn(x) + Qn(x) 為
0 1 2 3 n p0+q0 p1+q1 p2+q2 p3+q3 pn+qn
-
-
當我們遇見係數多項式的時候
S(x) = 1 + 3X10000+2X20000
這時候在使用上面的的方法未免有點太浪費空間了
所以我們應只是記錄係數不為零的項
0 1 2 1 3 2 0 10000 20000 第二行表示多項式的係數
第三行表示多項式的指數
線性表 A = ((7,0),(3,1),(9,8),(5,17))
線性表 B = ((8,1),(22,7),(-9,8))
-
建立一個數組c
-
分別從頭遍歷比較a和b的每一項
指數相同,對應係數相加,若其和不為零,則在c中增加一個新項
指數不同,則將指數較小的項複製到c中
-
當一個多項式遍歷完畢後,將另一個剩餘的項依次複製到c中即可
-
-
順序儲存結構存在的問題
-
儲存空間分配不靈活
-
運算的空間複雜度高
解決方法: 採用鏈式儲存結構,動態分配記憶體
-
-
總結
- 線性表中資料元素的型別可以是簡單型別(單純的數字),也可以是複雜型別(有字串)
- 許多實際應用問題所涉及的基本操作有很大的相似性,不應為每個具體單位單獨編寫一個程式
- 從具體應用中抽象出共性的邏輯結構和基本操作(抽象型別),然後實現其儲存結構和基本操作
抽象資料型別線性表
-
定義
ADT liet
{
資料物件:D = {ai | ai 屬於Elemset,(i = 1,2,3,····n,n>=0)}
資料關係:R = {<ai-1, ai>| ai-1, ai屬於D(i = 1,2,····,n)}
基本操作:
initlist(&l);
DestroyList(&l);
ClearList(&l);
ListEmpty(L);
}ADT list
initlist(&l)
操作結果:構造一個空的線性表
DestroyList(&l)
初始條件:線性表L已經存在
操作結果:銷燬線性表L;
ClearList(&l)
初始條件:線性表L已經存在
操作結果:將線性表L重置為空表
ListEmpty(L)
初始條件:線性表L已經存在。
操作結果: 若線性表L為空表,則返回True,否則返回False
順序表
第二週第八節
順序表的順序儲存表示
- 地址連續
- 依次存放
- 隨機存取
- 型別相同
- 所以我們可以使用一維陣列來表示順序表
- 但是順序表和連結串列都屬於線性表,而線性表長度可以變換,但是陣列長度不可以動態定義,所以我們需要使用一變數來表示順序表的長度
#define LIST_LENGTH 100
typedef struct
{
int realpart;
int imagpart;
}ElemType;
typedef struct
{
ElemType elem[LIST_LENGTH];
int length;
}Sqlist;
- 上述程式碼可以表示線性表的模板, ElemType 可以變化你需要的資料型別, LIST_LENGTH表示動態的陣列的長度,length表示實際的陣列的長度
- 有兩種順序表的定義形式,一種是上面的直接定義,資料靜態分配,還有一種是下面的
typedef struct
{
ElemType *data;
int length;
}Sqlist;
- 這種定義形式,在定義的時候沒有給陣列分配記憶體,需要在使用的時候靜態分配一下
Sqlist L;
L.data = (ElemType*)malloc(sizeof(Elemtype)*MaxSize);
補充(順序表的操作)
#include <stdio.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
// Status 是函式的型別,其值是函式結果狀態程式碼
typedef int Status;
typedef char ElemType;
// 線性表的初始化
Status InitList_Sq(SqList &L)
{
L.elem = new ElemType[MAXSIZE];
if(!L.elem)exit(OVERFLOW);
L.lengh = 0;
return OK;
}
// 銷燬
void DsetroyList(SqList &L)
{
if(L.elem)
delete L.elem; // 釋放空間
}
// 清空線性表L
void ClearList(SqList &L)
{
L.length = 0; // 將線性表的長度置為零
}
// 得到i位置上的元素
int GetElem(SqList L, int i, ElemType &e)
{
// 判斷i的值是否合理,若不合理,返回ERROR
if(i < 1 || i < L.length)
return ERROR;
e = L.elem[i-1];
return OK;
}
順序表的查詢演算法分析
順序查詢法:
將表中元素進行遍歷,依次比較如果查找出來則返回對應的記錄, 如果沒找到則返回0;
例如一個長度為7的陣列進行查詢,最少找一次,最多找7次,那麼平均一共找4次即可。
-
平均查詢長度ASL(Average Search Length)
- 為了確定記錄在表中的位置,需要與給定值進行比較的冠軍字的個數的期望值叫做查詢演算法的平均查詢長度
-
對含有n個記錄的表,查詢成功時:
ASL = (i從1到n加)PiCi = (n+1)/2;
Pi第i個記錄被查詢的概率
Ci找到第i個記錄需要比較的次數
例如上面的長度為7的陣列查詢到平均長度為1/7乘1 + 1/7乘2 + ····1/7乘7 每一個的查詢的概率都是1/7, 只是查詢到需要的次數不同。
順序表的插入演算法分析
-
插入位置在最後:這接放在最後即可
-
插入位置在中間:讓別插入的位置的後面的元素依次向後移
-
插入位置在最前面: 讓所有位置依次往後移,給這個位置移出來地方
-
演算法思想
-
插入位置的判斷(0-陣列長度)
-
判斷順序表的儲存空間是否已滿,若已經滿了則返回ERROR
-
將第n至i為的元素依次向後移動一個位置,空出第i個位置
-
將要插入的新元素e放入第i個位置
-
表長加一
Status ListInsert_Sq(SqList &L, int i,ElemType e) { if( i < 1 || i > L.length + 1)return ERROR; if(L.length == MAXSIZE) return ERROR; for(j = L.length-1; j >= i-1; j--) { L.elem[j+1] = L.elem[j]; } L.elem[i-1] = e; L.length++; return OK; }
-
時間分析
-
若插入在為節點則無需移動(特別快)
-
若插入在首節點。則表中元素都向後移(特別慢)
-
若要考慮在各種位置插入(共n+1種情況)
E(ins) = 1/(n+1)(從1到n+1)[n-i+1] = 1/(n+1) *(0+1+2+3+·····n) = 1/(n+1) * n(n+1)/2 = n/2;
-
時間複雜度O(n);
-
-
-
順序表刪除
- 刪除位置在最後:直接刪除即可,length-1;
- 刪除位置在中間:需要刪除該位置的元素,並將該位置後面的元素依次前移
- 刪除位置在最前面:刪除完畢後依次前移即可
- 演算法思想
- 判斷刪除位置i是否合法(合法值為1<=i<=n)
- 將欲刪除的元素保留在e中
- 將第i+1至第n位的元素依次前移一個位置
- 表長減一,刪除成功後返回OK
- 演算法的實現
Status ListDelete_Sq(SqList &L, int i, int e)
{
if((i<1)||(i>L.length))return ERROR;
e = L.elem[i-1];
for(j = i;j <=L.length-1; j++)
L.elem[j-1] = L.elem[j];
L.length--;
return e;
}
-
時間消耗分析
i=1 i=2 i=3 ···· i = n n-1 n-2 n-3 ···· 0 E(del) = 1/n (i從1到n)(n-i) = 1/n * (n-1)*n/2 = (n-1)/2;
時間複雜度為O(n);
順序表總結
- 順序表的特點:
- 利用資料元素的儲存位置表示線性表中相鄰資料元素之間的前後關係即線性表的邏輯結構與儲存結構一致
- 在訪問線性表時,可以快速的計算出任何一個數據元素的儲存地址,因此可以粗略的認為,訪問每個元素所化的時間相等
- 這種存取元素的方法稱為隨機存取法
- 順序表的操作演算法分析
- 時間複雜度
- 查詢,插入,刪除,演算法的平均時間複雜度為O(n)
- 空間複雜度
- S(n) = O(1)
- 沒有輔助空間
- 時間複雜度
類c語言補充(c語言版)
- 其中Elemtype表示該資料型別的長度, MaxSize表示陣列的長度;
- sizeof(Elemtype)*MaxSize 表示需要從記憶體中獲取這麼大的地址,而這麼大的地址將要去幹什麼,是存除整形,浮點型,還是結構體,這就需要我們前面括號裡的(ElemType *)來表示
- 使用ElemType*除了表示為這些記憶體空間需要存放的資料型別, 還能進行資料型別的強制轉換,因為L.data 應該儲存陣列的首地址
- malloc(m)開闢m位元組長度的地址空間,並返回這段空間的首地址
- sizeof(x),計算變數x的長度
- free(p),釋放指標p所指變數的記憶體,徹底刪除一個變數
- 都需要載入標頭檔案 <stdlib.h>
類c語言補充(c++版)
-
使用new來代替c語言中malloc的臃長程式碼
new 型別名T(初始值表)
功能: 申請用於存放T型別物件的記憶體空間,並依初值列表來賦初值
結果值:
成功:T型別的指標,指向新分配的記憶體
失敗: 0(NULL)
-
delete 指標p
功能:
釋放指標p所指向的記憶體,p必須是new操作的返回值
-
函式呼叫時傳送給形參表的實參必須是形參三個一致
型別,個數,順序
-
引數傳遞時有兩種方式
-
傳值方式:引數為整型,實型,字元型等;
-
傳地址
-
引數為指標變數
-
引數為引用型別
這個是c++中的語法
void main() { int i = 5; int &j = i; i = 7; }
則j也等於7; 其中第二個步驟的含義就是j 是 i 的別名,他們倆是同一個東西,改一動二
- 以下是交換a,b值的方法
// 第一種 int main() { float a,b; swap(&a,&b); } void swap(float *m, float *n) { float temp; temp = *m; *m = *n; *n = temp; } // 第二種 c++方法 int main() { float a,b; swap(a,b); } // 這裡就是交換ab的值, 使用c++中的語法 & 來給a,b取別名,從而交換mn的值來交換ab的值 void swap(float &m, float &n) { float temp; temp = m; m = n; n = temp; }
-
-
-
引數為陣列名
-
引用型別做形參的三點說明
- 傳遞引用給引數與傳遞指標的效果是一樣的,形參變換實參也發生變化
- 引用型別作為形參,在記憶體中並沒有產生實參的副本,它直接對實參操作;而一般變數作為引數,形參與實參就佔用不同的儲存單元,所以形參變數的值是實參變數的副本,因此,當引數傳遞資料量較大時,用引用比用一般變數傳遞引數的時間和空間效率都好
- 指標引數雖然也能達到與使用引用的效果,但在被調函式中需要重複使用“*指標變數名”的形式進行運算,者恆容易產生錯誤且程式閱讀性較差,另一方面,再主調函式的呼叫點出,必須使用變數的地址作為實參
連結串列
連結串列的鏈式表示和實現
- 鏈式儲存結構
- 節點在儲存器中的位置是任意的,即邏輯上相鄰的資料元素,在物理上不一定相鄰
- 線性表的鏈式表又稱為非順序映像或鏈式映像
- 用一組物理位置任意的儲存單元來存放連結串列的元素
- 這組儲存單元可以是連續的,也可以是分散的,
- 連結串列中元素的邏輯次序和物理次序不一定相同
- 與鏈式儲存有關的術語
- 節點:
- 資料元素的儲存映像,由資料域和指標域組成
- 連結串列:
- n個節點由指標鏈組成一個連結串列
- 他是線性表的鏈式儲存映像,稱為線性表的鏈式儲存結構
- 節點:
連結串列形式
-
單鏈表
- 結點只有一個指標域的連結串列,稱為單鏈表,或線性連結串列
-
雙鏈表
- 節點有兩個指標域的連結串列稱為雙鏈表
-
迴圈連結串列
- 首位相接的連結串列稱為迴圈連結串列
-
頭指標,頭結點,首元節點
- 頭指標:指向連結串列中的第一個結點的指標
- 首元節點:是指向連結串列中儲存第一個資料元素a1的節點
- 頭結點:是在連結串列中的首元節點之前附設的一個節點
-
連結串列的兩種形式
-
帶頭結點的
-
不帶頭結點的
-
討論:在連結串列中設定頭結點有什麼好處
-
便於首元節點的處理
首元節點的地址儲存在頭結點的指標域中,所以在連結串列中第一個位置操作和其他位置相同,無需進行任何處理
-
便於空表和非空表的統一處理
無論連結串列是否為空,頭指標都是指向頭結點的非空指標,因此空表和非空表的處理也就同一了
-
-
討論:頭結點的資料域內裝的是什麼
頭結點的資料域可以為空,可以存放表的長度等附加資訊,但是此節點不能計入連結串列長度值
-
-
如何表示空表
- 無頭結點的時候: 頭指標為空時表示空表
- 有頭結點的時候:頭結點的指標域為空表示空表
-
連結串列的特點
- 節點在儲存器中的位置是任意的,即邏輯上相鄰的資料元素在物理上不一定相鄰
- 訪問時只能通過頭指標進入連結串列,並通過每一個節點的指標域依次向後順序掃描其餘節點,所以尋找第一個,和最後一個所花費的時間不同順序存取法
單鏈表
-
單鏈表的命名
- 單鏈表是由表頭唯一確定,因此單鏈表可以用頭指標的名字來命名,若頭指標名是L,則把連結串列稱為表L
-
單鏈表基本操作的實現
-
單鏈表的初始化
- 即構造一個空表
-
演算法步驟
- 生成新節點作頭結點,用頭指標L指向頭結點
- 將頭結點的指標域置空
-
演算法描述
// 初始化 Status LnitList_L(LinkList &L) { L = new LNode; // 或者 L = (LNode*)malloc(sizeof(LNode)); L->next = NULL; return OK; }
-
補充演算法------判斷連結串列是否為空
-
空表: 連結串列中無元素,稱為空連結串列(頭指標和頭結點仍然存在)
-
演算法思路:判斷頭結點的指標域是否為空
int ListEmpty(LinkList L) { if(L->next) return 0; else return 1; }
-
補充演算法-------單鏈表的銷燬,連結串列銷燬後不存在(頭結點,頭指標都不存在)
-
演算法思路:從頭指標開始,依次釋放所有的結點,這裡需要有兩個指標變數,一個指向頭結點,另一個指向頭結點的下一個
-
迴圈結束條件 頭指標為空
Status DeleteList_L(LinkList *L)//銷燬單鏈表L { Londe *p; p = (LNode*)malloc(sizeof(LNode)); while(L)//迴圈結束條件L不為空 { p = L;//先將頭結點地址賦值給臨時變數 L = L->next;//再將頭指標指向下一個 free(p);//釋放該節點 } return OK; }
-
補充演算法 ------ 清空連結串列
-
連結串列仍然存在,但連結串列中無元素,成為空連結串列(頭結點,頭指標依然存在)
-
演算法思路,依次釋放所有結點,並將頭結點指標域設定成空
-
迴圈結束條件 指向最後一個元素
Status ClearList_L(LinkList *L)//清空單鏈表L { Londe *p,*q; p = L->next; while(p)//迴圈結束條件指向最後一個了 { q = p->next; free(p);//釋放該節點 p = q; } L->next == NULL; return OK; }
-
補充演算法 ---- 求單鏈表的表長
-
思路:從首元節點開始,依次計數所有結點
int ListLength_L(LinkList L) // 返回值中資料元素的個數 { int i = 0; LNode *p; p = L->next; while(p) { i++; p = p->next; } return i; }
-
取值-----取單鏈表中第i個元素的內容
-
思路:從連結串列的頭指標出發順著鏈域next逐個結點往下搜尋,知道索搜到第i個結點為止,因此,連結串列不是隨機存取結構
-
步驟:
-
從第一個結點(L->next)順鏈掃描用指標p記錄當前所掃描到的結點,p的初值為p=L->next
-
.j做計數器,累計當前掃描過的節點數,j的初值為1
-
當p指向掃描到的下一節點的時候,計數器j+1
-
當j === i時找到,退出程式
Status GetElem_L(LinkList L, int i, ElemType &e) // 獲取線性表中的某個資料元素,通過變數e返回 { p = L->next; while(p&&j<i) { p = p->next; j++; } if( j == i) { e = p->data } else { return ERROR; } }
-
查詢:
- 按值查詢:根據指定資料獲取該資料所在的位置(該資料的地址)
- 按值查詢:根據指定資料獲取該資料所在的位置序號(是第幾個資料元素)
-
插入:在第i個結點前插入新結點
Lnode *LocateElem_L(LinkList L, Elemtype e) // 線上性表中查詢值為e的資料元素 // 找到,則返回L中值為e的資料元素的地址,查詢失敗返回NULL { LinkList p; p = L->next; while(p&&p->data!=e) { p = p->next; } // 返回地址 return p; } Lnode *LocateElem_L(LinkList L, Elemtype e) // 線上性表中查詢值為e的資料元素 // 找到,則返回L中值為e的資料元素的位置,查詢失敗返回NULL { int i = 1; LinkList p; p = L->next; while(p&&p->data!=e) { p = p->next; i++ } // 返回地址 if( p!=NULL) { return i; } else { return 0; } }
-
插入:在第i個結點前插入新節點
Status ListInsert_L(LinkList &L, int i, Elemtype e) // 在L中第i個元素之前插入資料元素e { int a = 0; LinkList p, q; p = L; while(a < i-1 && p) { p = p->next; a++; } if( a == i-1 ) { q = (LNode*)malloc(sizeof(LNode)) q->data = e; p->next = q->next; p->next = q; // 這兩步交換位置的演算法不能顛倒先後位置,否則會導致,資料丟失,交換失敗 return OK; } else { return ERROR } }
-
刪除 刪除第i個結點
Status ListDelete_L(LinkList &L, int i, Elemptype &e) // 在L中刪除第i個元素 { int a = 0; LinkList p, q; p = L; while(p->next && a <i-1) { p=p->next; a++; } if( !(p->next) || a >i-1) return ERROR; q = p->next; e = q->data;//儲存要刪除的元素的位置 p->next = q->next; free(q); return OK; }
-