數據結構和算法(一)線性表實現

數據結構和算法(1)線性表實現git

數據結構和算法(2)單向循環鏈表的建立插入刪除實現github

數據結構和算法(3)雙向鏈表與雙向循環鏈表的實現面試

數據結構和算法(4)鏈表相關面試題算法

數據結構和算法(5)棧和隊列的操做和實現shell

數據結構和算法(6)隊列的操做和實現swift

@TOC數組

1. 數據結構簡介

數據結構簡介

線性表彙總

1.1 抽象數據類型

1.1.1 數據類型

數據類型:是指一組性質相同值的激活以及定義在此激活的一些操做的總稱。數據結構

在C語言中,按照取值的不一樣,數據類型能夠分爲2類:數據結構和算法

  • 原子類型: 是不能夠在分解的基本數據類型,包含整型,浮點型,字符型等。
  • 結構類型: 由若干類型組合而成,是能夠再分解的,例如,整型數組就是由若干整型數據組成的。

1.1.2 抽象數據類型

抽象:是抽取出事物具備的廣泛的本質。它是抽出問題的特徵二忽略非本質的細節,是對具體事物的一個歸納。抽象是一種思考問題的方式,它隱藏繁雜的細節,只保留實現目標必須要的信息。函數

抽象數據類型:是指一個數學模型以及定義在該模型上的一組操做;例如,咱們在編寫計算機繪圖軟件系統是,常常會使用到座標。也就是說,會常用x,y來描述縱橫座標。而在3D系統中,Z深度就會出現。既然這3個整型數字是始終出如今一塊兒,那麼就能夠定義成一個Point的抽象數據類型。它有x,y,z三個整型變量。這樣開發者就很是方便操做Point數據變量。

抽象數據類型能夠理解成實際開發裏常用的結構體和類;根據業務需求定義合適的數據類型和動做。

在咱們生活中有哪些線性表的例子呢?

例如: 26個字母表;例如學生基本信息表。每一個學生爲一個數據元素,包含學號,姓名,專業等數據項目。知足數據元素不一樣,可是在同一個線性表中的元素一定具備相同的特色,即屬於同一數據對象,相鄰數據元素之間存在這個序列關係,注入此類有(n >= 0 )個數據特性相同的元素構成的有限序列稱爲:「線性表」

/* 數據: 程序的操做對象,用於描述客觀事物. 數據的特色: 1️⃣ 能夠輸入到計算機 2️⃣ 能夠被計算機處理 數據項: 一個數據元素由若干數據項組成 數據元素: 組成數據的對象的基本單位 數據對象: 性質相同的數據元素的集合(相似於數組) 結構: 數據元素之間不是獨立的,存在特定的關係.這些關係便是結構; 數據結構:指的數據對象中的數據元素之間的關係 */
#include <stdio.h>

//聲明一個結構體類型
struct Teacher{     //一種數據結構
    char *name;     //數據項--名字
    char *title;    //數據項--職稱
    int  age;       //數據項--年齡
};


int main(int argc, const char * argv[]) {
   
    struct Teacher t1; //數據元素; struct Teacher tArray[10]; //數據對象; t1.age = 18; //數據項 t1.name = "CC"; //數據項 t1.title = "講師"; //數據項 printf("老師姓名:%s\n",t1.name); printf("老師年齡:%d\n",t1.age); printf("老師職稱:%s\n",t1.title); return 0; } 複製代碼

線性表中的元素的個數n定義爲線性表的長度,若是n=0則稱爲空表

對應非空的線性表和線性結構,其特色以下:

  • 存在惟一的一個被稱做「第一個」的數據元素
  • 存在惟一一個被稱做「最後一個」的數據元素。
  • 除了第一個以外,結構中的每一個數據元素都有一個前驅。
  • 除了最後一個以外,結構中的每一個數據元素都有一個後繼。

1.2 數據結構基本術語

1.2.1 數據結構基本術語

數據的構成:基本數據單位

數據的構成

  • 數據結構的邏輯結構:按邏輯分爲:集合結構,線性結構,樹形結構,圖形機構等。

集合結構: 集合結構中的數據元素除了同屬於一個集合外,它們之間沒有其餘關係,它們之間惟一的相同點就是"同屬於一個集合"。

集合結構

線性結構: 線性結構中的數據元素之間的關係是一對一的。經常使用的線性結構有:線性表、棧、隊列、雙隊列、數組、串。

線性結構

樹形結構: 樹形結構中的數據元素是一對多的層級關係。常見的樹形結構: 二叉樹、B樹、哈夫曼樹、紅黑樹等。

樹形結構

圖形結構: 圖形結構中的數據元素之間的關係是多對多的。常見的圖形結構:鄰近矩陣、鄰接表。

圖形結構

  • 數據結構按物體機構分爲:順序存儲結構,鏈式存儲結構。

順序存儲結構:指把數據元素存放在地址連續的存儲單元裏,其數據間的邏輯關係和物理關係是一致的。好比一個數組,它的元素是一個接一個,在內存空間中的地址也是連續的,咱們能夠經過數組的下標訪問每個元素,也可使用地址遞增的方式訪問

順序存儲結構

//順序表結構
typedef struct KSequenceList {
    KElementType *data;
    int length;
}KSList;
複製代碼

鏈式存儲結構: 是把數據元素放在任意的存儲單元裏,這組存儲單元能夠是連續的,也能夠是不連續的。數據元素的存儲關係並不能反映邏輯關係,所以須要用一個指針存放數據元素的地址,這樣經過地址就能夠找到相關聯數據元素的位置。

鏈式存儲結構

1.2.2 數據結構與算法關係

數據結構和算法的關係

算法是什麼?

算法就是解決特定問題求解步驟的描述,在計算機中表現爲指令的有限序列,而且每一個指令表示一個或者多個操做。

算法有如下特性:

  • 輸入輸出: 算法具備零個或者多個輸入,至少有一個或多個輸出。
  • 有窮性:有窮性指的是算法在執行有限的步驟以後,自動結束而不會出現無限循環,且每個步驟都在可接受的時間內完成。
  • 肯定性:肯定性是指算法的每個步驟都具備肯定的含義,不能出現二義性。 算法在必定條件下,只有一條執行路徑,相同的輸入只能有惟一的輸出結果。
  • 可行性:算法的每一步都必須是可行的,即每一步都能經過執行有限次數完成

算法設計要求:

  • 正確性:算法的正確性是指算法至少應該具備輸入,輸出和加工處理無歧義性,能正確反映問題的需求、可以獲得問題的正確答案。正確性分爲4個層次: - 算法程序沒有語法錯誤; - 算法程序對於合法的輸入數據可以產生知足要求的輸出結果; - 算法程序對於非法的輸入數據可以得出知足規格說明的結果; - 算法程序對於精心選擇的,甚至刁鑽的測試數據都有知足要求的輸出結果;
  • 可讀性:: 算法設計的另外一個目的是爲了便於閱讀,理解和交流。可讀性高有助於人們理解算法,晦澀難懂的算法每每隱含錯誤,不容易發現,而且難於調試和修改。
  • 健壯性:一個好的算法還應該能對輸入數據的不合法的狀況作出合適的處理,考慮邊界性,也是在寫代碼常常要作的一個處理。當輸入數據不合法時,算法也能作出相關處理,而不是產生異常和莫名其妙的結果。
  • 時間效率高和存儲量低:用最少的存儲空間和最少的時間,辦成一樣的事,就是好算法

1.2.3 時間複雜度和空間複雜度

1.2.3.1 時間複雜度:

使用高級程序語言編寫的程序在計算機上運行時所消耗的時間取決於下列的因素:

  • 算法採用的策略、方法
  • 編譯產生的代碼質量
  • 問題的輸入規模
  • 機器執行指令的速度

算法的時間複雜度的定義以下: 在進行算法分析時,語句的總執行次數T(n)是關於問題規模n的函數,進而分析T(n)隨着n變化狀況並肯定T(n)的數量級。算法的時間複雜度,也就是算法的時間量度,即爲T(n) = O(f(n))。它表示隨問題規模n的增大,算法執行時間的增加率和f(n)的增加率相同,稱做算法的漸近時間複雜度,簡稱爲時間複雜度。其中f(n)是問題規模n的某個函數。 大寫O( )來體現算法時間複雜度的記法,咱們稱之爲大O記法。 推導大O階的方法:

  • 用常數1取代運行時間中全部加法常數

  • 在修改後的運行次數函數中,只保留最高階項

  • 若是在最高階項存在且不是1,則去除與這個項相乘的常數

  • 常數階

int sum = 0, n = 100;
sum = (1 + n) * n / 2;
printf("%d", sum);
複製代碼

這個算法的每行代碼都會執行一次,運行次數函數是f(n) = 3,依據推導大O階的方法,第一步就是將常數替換爲1,因爲沒有最高階項,因此該算法的時間複雜度爲O(1)。 須要注意的是,常數階的算法無論常數是多少,咱們都記作O(1),並無O(2)、O(3)之類的複雜度。

  • 線性階
for (int i = 0; i < n; i++) {
    // 時間複雜度爲O(1)的操做 
}
複製代碼

分析算法的複雜度,就是要分析循環結構的運行狀況。上述代碼的時間複雜度爲O(n),是由於循環體中的代碼要執行n次。

  • 對數階
int i = 1;
while (i < n) {
    i = i * 2;
}
複製代碼

在循環體內,i每次都乘以2倍,即爲2^x = n,能夠得出次數x爲以2爲低n的對數x = log2n。根據推導大O階的方法,去除最高階項的常數,即爲O(logn)。

  • 平方階
for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        // 時間複雜度爲O(1)的操做 
    }
}
複製代碼

循環嵌套n*n,時間複雜度爲O(n^2)。

例以下面函數的執行次數:

求解函數執行次數

上表對應的函數執行次數以下:

函數執行次數結構

時間複雜實際就是評估算法執行的次數:

時間複雜度比較
O(1) < O(log n) < O(n) < O(nlog n) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)

  • 時間複雜度計算練習
#include <stdio.h>
/*大O表示法 1. 用常數1取代運行時間中全部常數 3->1 O(1) 2. 在修改運行次數函數中,只保留最高階項 n^3+2n^2+5 -> O(n^3) 3. 若是在最高階存在且不等於1,則去除這個項目相乘的常數 2n^3 -> n^3 */

/* 時間複雜度術語: 1. 常數階 2. 線性階 3. 平方階 4. 對數階 5. 立方階 6. nlog階 7. 指數階(不考慮) O(2^n)或者O(n!) 除非是很是小的n,不然會形成噩夢般的時間消耗. 這是一種不切實際的算法時間複雜度. 通常不考慮! */

/* 1. 常數階時間複雜度計算 O(1) */
//1+1+1 = 3 O(1)
void testSum1(int n){
    int sum = 0;                //執行1次
    sum = (1+n)*n/2;            //執行1次
    printf("testSum1:%d\n",sum);//執行1次
}

//1+1+1+1+1+1+1 = 7 O(1)
void testSum2(int n){
    int sum = 0;                //執行1次
    sum = (1+n)*n/2;            //執行1次
    sum = (1+n)*n/2;            //執行1次
    sum = (1+n)*n/2;            //執行1次
    sum = (1+n)*n/2;            //執行1次
    sum = (1+n)*n/2;            //執行1次
    printf("testSum2:%d\n",sum);//執行1次
    
}
//x=x+1; 執行1次
void add(int x){
    x = x+1;
}


/*2.線性階時間複雜度*/
//x=x+1; 執行n次 O(n)
void add2(int x,int n){
    for (int i = 0; i < n; i++) {
        x = x+1;
    }
}

//1+(n+1)+n+1 = 3+2n -> O(n)
void testSum3(int n){
    int i,sum = 0;               //執行1次
    for (i = 1; i <= n; i++) {   //執行n+1次
        sum += i;                //執行n次
    }
    printf("testSum3:%d\n",sum);  //執行1次
}

/*3.對數階*/
/*2的x次方等於n x = log2n ->O(logn)*/
void testA(int n){
    int count = 1;         //執行1次
    //n = 10
    while (count < n) {
        count = count * 2;
    }
    
}


/*4.平方階*/
//x=x+1; 執行n*n次 ->O(n^2)
void add3(int x,int n){
    for (int i = 0; i< n; i++) {
        for (int j = 0; j < n ; j++) {
            x=x+1;
        }
    }
}

//n+(n-1)+(n-2)+...+1 = n(n-1)/2 = n^2/2 + n/2 = O(n^2)
//sn = n(a1+an)/2
void testSum4(int n){
    int sum = 0;
    for(int i = 0; i < n;i++)
        for (int j = i; j < n; j++) {
            sum += j;
        }
    printf("textSum4:%d",sum);
    
}

//1+(n+1)+n(n+1)+n^2+n^2 = 2+3n^2+2n -> O(n^2)
void testSum5(int n){
    int i,j,x=0,sum = 0;           //執行1次
    for (i = 1; i <= n; i++) {     //執行n+1次
        for (j = 1; j <= n; j++) { //執行n(n+1)
            x++;                   //執行n*n次
            sum = sum + x;         //執行n*n次
        }
    }
    printf("testSum5:%d\n",sum);
}


/*5.立方階*/
void testB(int n){
    int sum = 1;                         //執行1次
    for (int i = 0; i < n; i++) {        //執行n次
        for (int j = 0 ; j < n; j++) {   //執行n*n次
            for (int k = 0; k < n; k++) {//執行n*n*n次
                sum = sum * 2;          //執行n*n*n次
            }
        }
    }
}

int main(int argc, const char * argv[]) {
    
    testSum1(100);
    testSum2(100);
    testSum3(100);
    
    return 0;
}
複製代碼

1.2.3.2 空間複雜度:

算法的空間複雜度

算法的空間複雜度經過計算算法所須要的存儲空間實現,算法空間複雜度的計算公式記作:S(n) = n(f(n)), 其中n爲問題的規模, f(n) 爲語句關於n 所佔內存空間的函數。

  • 空間複雜度練習
/* 程序空間計算因素: 1. 寄存自己的指令 2. 常數 3. 變量 4. 輸入 5. 對數據進行操做的輔助空間 在考量算法的空間複雜度,主要考慮算法執行時所須要的輔助空間. 空間複雜度計算: 問題: 數組逆序,將一維數組a中的n個數逆序存放在原數組中. */

#include <stdio.h>

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
   
    int n = 5;
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    
    //算法實現(1)
    int temp;
    for(int i = 0; i < n/2 ; i++){
        temp = a[i];
        a[i] = a[n-i-1];
        a[n-i-1] = temp;
    }

    for(int i = 0;i < 10;i++)
    {
        printf("%d\n",a[i]);

    }
    
    //算法實現(2)
    int b[10] = {0};
    for(int i = 0; i < n;i++){
        b[i] = a[n-i-1];
    }
    for(int i = 0; i < n; i++){
        a[i] = b[i];
    }
    for(int i = 0;i < 10;i++)
    {
        printf("%d\n",a[i]);
        
    }
    
    return 0;
}
複製代碼

1.3 線性表

1.3.1 線性表之順序表

ADT list{ Data:線性表的數據對象集合爲{a1,a2,a,......an},每一個元素的類型均爲DataType,其中,除了第一個元素a1 外,每個元素有且只有一個直接前驅元素,除了最後一個元素an 外,每一個元素有且只有一個直接後續元素,數據元素之間的關係是一對一的關係。 OPeration(操做) InitList(&L) 初始化表 操做結果:初始化操做,創建一個空的線性表L DestroyList(&L) 銷燬表 初始條件:線性表L已存在 操做結果:銷燬線性表L ClearList(&L) 清空表 初始條件:線性表L已存在 操做結果:將L重置爲空表 ListEmpty(L) 表是否爲空 初始條件:線性表L已存在 操做結果:若L爲空表,則返回true,不然返回false ListLength(L) 表長度(元素個數) 初始條件:線性表L已存在 操做結果:返回L中數據元素的個數 ...... GetElem(L,i,&e) 獲取元素 初始條件: 線性表L已存在,且1<=i<ListLength(L) 操做結果: 用e返回L中第i個數據元素的值; LocateElem(L,e) 初始條件: 線性表L已存在 操做結果: 返回L中第1個值與e相同的元素在L中的位置. 若數據不不存在則返回0; PriorElem(L, cur_e,&pre_e); 初始條件: 線性表L已存在 操做結果: 若cur_e是L的數據元素,且不不是第⼀一個,則⽤用pre_e返回其前驅,不然操做失敗. NextElem(L, cur_e,&next_e); 初始條件: 線性表L已存在 操做結果: 若cur_e是L的數據元素,且不不是最後⼀一個,則⽤用next_e返回其後繼,不然操做失敗. ...... ListInsert(L,i,e); 初始條件: 線性表L已存在,且1<=i<=listLength(L) 操做結果: 在L中第i個位置以前插⼊入新的數據元素e,L⻓長度加1. ListDelete(L,i); 初始條件: 線性表L已存在,且1<=i<=listLength(L) 操做結果: 刪除L的第i個元素,L的⻓長度減1. TraverseList(L); 初始條件: 線性表L已存在 操做結果: 對線性表L進⾏行行遍歷,在遍歷的過程當中對L的每一個結點訪問1次. }ADT List.

1.3.1.1 順序表基本操做實現

順序表完整實現代碼點擊這裏下載:順序表基本操做實現

1.3.1.1.1 順序表結構定義
//順序表結構
typedef struct KSequenceList {
    KElementType *data;
    int length;
}KSList;
複製代碼
1.3.1.1.2 順序表初始化
// 1 順序表初始化
KStatus initSequenceList(KSList *L) {
    
    //爲順序表分配一個大小爲MAXSIZE 的數組空間
    L->data = malloc(sizeof(KElementType) * MAXSIZE);
    //存儲分配失敗直接退出
    if(!L->data) return ERROR;
    //空表長度爲0
    L->length = 0;
    
    return OK;
}
複製代碼
1.3.1.1.3 順序表的插入
// 2 順序表的插入
// 初始條件:順序線性表L已存在,1≤i≤ListLength(L);
// 操做結果:在L中第i個位置以前插入新的數據元素e,L的長度加1
KStatus insertElement(KSList *L, int i, KElementType e) {
    
    //邊界條件判斷
    //1.1 出入的索引 i 合法性判斷, 不能超過鏈表總長度
    if((i < 1) || (i > L->length+1)) return ERROR;
    //1.2 存儲空間是否已滿
    if(L->length == MAXSIZE) return ERROR;
    
    //1.3 插入數據若是不在表尾部,則先日後移動騰出位置給要插入的元素
    if(i <= L->length) {
        for(int j = L->length-1; j >= i-1; j--) {
            //插入位置後面的元素都移動1個位置,讓出第i個位置
            L->data[i+1] = L->data[i];
        }
    }
    
    //1.4 將新元素賦值到騰出的位置,完成插入
    L->data[i-1] = e;
    
    //1.5 鏈表長度增長1
    ++L->length;
    
    return OK;
}
複製代碼
1.3.1.1.4 順序表的取值
// 3. 順序表的取值
KStatus getElement(KSList L, int i, KElementType *e) {
    
    //邊界條件判斷,i不能超過總長度
    if(i < 1 || i > L.length) return ERROR;
    
    //直接取出第i個元素的值,索引下標對應爲i-1 (下標默認從0開始)
    *e = L.data[i-1];
    
    return OK;
}
複製代碼
1.3.1.1.5 順序表刪除
// 4. 順序表刪除
//初始條件:順序線性表L已存在,1≤i≤ListLength(L)
//操做結果: 刪除L的第i個數據元素,L的長度減1
KStatus deleteElement(KSList *L, int i) {
    
    //邊界條件判斷
    //線性表是否爲空
    if(L->length == 0) return ERROR;
    //i值合法性判斷
    if((i < 1) || (i > L->length+1)) return ERROR;
    
    for (int j = i; j < L->length; j++) {
        //被刪除的元素後面的全部元素往前移動一個位置
        L->data[j-1] = L->data[j];
    }
    
    //表長度減1
    L->length--;
    
    return OK;
}
複製代碼
1.3.1.1.6 清空順序表
// 5. 清空順序表
//初始條件:順序線性表L已存在。操做結果:將L重置爲空表
KStatus clearList(KSList *L) {
    L->length = 0;
    return OK;
}
複製代碼
1.3.1.1.7 判斷順序表清空
// 6. 判斷順序表清空
//初始條件:順序線性表L已存在。操做結果:若L爲空表,則返回TRUE,不然返回FALSE
KStatus isListEmpty(KSList L) {
    return L.length == 0 ;
}
複製代碼
1.3.1.1.8 獲取順序表表長度
// 7. 獲取順序表表長度
int getListLength(KSList L) {
    return L.length;
}

複製代碼
1.3.1.1.9 遍歷順序表
// 8. 遍歷順序表
//初始條件:順序線性表L已存在
//操做結果:依次對L的每一個數據元素輸出
KStatus traverseList(KSList L) {
    for(int i = 0; i < L.length; i++) {
        printf("%d.\n",L.data[i]);
    }
    printf("\n");
    return OK;
}
複製代碼
1.3.1.1.10 查找順序表元素下標
// 9. 查找順序表元素下標
//初始條件:順序線性表L已存在
//操做結果:返回L中第1個與e知足關係的數據元素的位序。
//若這樣的數據元素不存在,則返回值爲0
int getElementIndex(KSList L, KElementType e) {
    //邊界條件判斷
    if (L.length == 0 ) return 0;
    
    //順序查找
    int i = 0;
    for(i = 0; i < L.length; i++) {
        if (L.data[i] == e) {break;}
    }
    
    if (i >= L.length) return 0;
    
    return i;
}
複製代碼
1.3.1.1.11 單元測試
// 10. 單元測試
void test() {
    KSList L1;
    //KSList L2;
    KElementType e;
    KStatus iStatus;
    
    //1.1 順序表初始化
    iStatus = initSequenceList(&L1);
    printf("初始化L1後: L.Length = %d\n", L1.length);
    
    //1.2 順序表數據插入
    for(int j=1; j <= 5;j++){
        iStatus = insertElement(&L1, 1, j);
    }
    printf("插入數據L1長度: %d\n",L1.length);
    
    //1.3 順序表取值
    getElement(L1, 5, &e);
    printf("順序表L1第5個元素的值爲:%d\n",e);
    
    //1.4 順序表刪除第2個元素
    deleteElement(&L1, 2);
    printf("順序表L1刪除第%d元素,長度爲%d\n",2,L1.length);
    
    //1.5 清空順序表
    iStatus = clearList(&L1);
    printf("清空L1後,L.length = %d\n",L1.length);
    
    //1.6 判斷List是否爲空
    iStatus = isListEmpty(L1);
    printf("L1是否空:i=%d(1:是 0:否)\n",iStatus);
    
    //1.8 遍歷打印順序表
    for(int j=1; j <= 5;j++){
        iStatus = insertElement(&L1, 1, j);
    }
    traverseList(L1);
}
複製代碼

輸出結構爲:

Hello, World!
初始化L1後: L.Length = 0
插入數據L1長度: 5
順序表L1第5個元素的值爲:446
順序表L1刪除第2元素,長度爲4
清空L1後,L.length = 0
L1是否空:i=1(1:是 0:否)
5.
0.
0.
446.
446.

Program ended with exit code: 0
複製代碼

1.3.1.2 線性順序表完整操做代碼

//
// main.c
// 001_LinearList
//
// Created by 孔雨露 on 2020/4/5.
// Copyright © 2020 Apple. All rights reserved.
//

#include <stdio.h>
#include "stdlib.h"
#include "math.h"
#include "time.h"

#define MAXSIZE 100
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

//KElementType類型根據實際狀況而定,這裏假設爲int
typedef int KElementType;
//KStatus是函數的類型,其值是函數結果狀態代碼,如OK等
typedef int KStatus;


//順序表結構
typedef struct KSequenceList {
    KElementType *data;
    int length;
}KSList;

// 1 順序表初始化
KStatus initSequenceList(KSList *L) {
    
    //爲順序表分配一個大小爲MAXSIZE 的數組空間
    L->data = malloc(sizeof(KElementType) * MAXSIZE);
    //存儲分配失敗直接退出
    if(!L->data) return ERROR;
    //空表長度爲0
    L->length = 0;
    
    return OK;
}

// 2 順序表的插入
// 初始條件:順序線性表L已存在,1≤i≤ListLength(L);
// 操做結果:在L中第i個位置以前插入新的數據元素e,L的長度加1
KStatus insertElement(KSList *L, int i, KElementType e) {
    
    //邊界條件判斷
    //1.1 出入的索引 i 合法性判斷, 不能超過鏈表總長度
    if((i < 1) || (i > L->length+1)) return ERROR;
    //1.2 存儲空間是否已滿
    if(L->length == MAXSIZE) return ERROR;
    
    //1.3 插入數據若是不在表尾部,則先日後移動騰出位置給要插入的元素
    if(i <= L->length) {
        for(int j = L->length-1; j >= i-1; j--) {
            //插入位置後面的元素都移動1個位置,讓出第i個位置
            L->data[i+1] = L->data[i];
        }
    }
    
    //1.4 將新元素賦值到騰出的位置,完成插入
    L->data[i-1] = e;
    
    //1.5 鏈表長度增長1
    ++L->length;
    
    return OK;
}


// 3. 順序表的取值
KStatus getElement(KSList L, int i, KElementType *e) {
    
    //邊界條件判斷,i不能超過總長度
    if(i < 1 || i > L.length) return ERROR;
    
    //直接取出第i個元素的值,索引下標對應爲i-1 (下標默認從0開始)
    *e = L.data[i-1];
    
    return OK;
}

// 4. 順序表刪除
//初始條件:順序線性表L已存在,1≤i≤ListLength(L)
//操做結果: 刪除L的第i個數據元素,L的長度減1
KStatus deleteElement(KSList *L, int i) {
    
    //邊界條件判斷
    //線性表是否爲空
    if(L->length == 0) return ERROR;
    //i值合法性判斷
    if((i < 1) || (i > L->length+1)) return ERROR;
    
    for (int j = i; j < L->length; j++) {
        //被刪除的元素後面的全部元素往前移動一個位置
        L->data[j-1] = L->data[j];
    }
    
    //表長度減1
    L->length--;
    
    return OK;
}

// 5. 清空順序表
//初始條件:順序線性表L已存在。操做結果:將L重置爲空表
KStatus clearList(KSList *L) {
    L->length = 0;
    return OK;
}

// 6. 判斷順序表清空
//初始條件:順序線性表L已存在。操做結果:若L爲空表,則返回TRUE,不然返回FALSE
KStatus isListEmpty(KSList L) {
    return L.length == 0 ;
}

// 7. 獲取順序表表長度
int getListLength(KSList L) {
    return L.length;
}

// 8. 遍歷順序表
//初始條件:順序線性表L已存在
//操做結果:依次對L的每一個數據元素輸出
KStatus traverseList(KSList L) {
    for(int i = 0; i < L.length; i++) {
        printf("%d.\n",L.data[i]);
    }
    printf("\n");
    return OK;
}

// 9. 查找順序表元素下標
//初始條件:順序線性表L已存在
//操做結果:返回L中第1個與e知足關係的數據元素的位序。
//若這樣的數據元素不存在,則返回值爲0
int getElementIndex(KSList L, KElementType e) {
    //邊界條件判斷
    if (L.length == 0 ) return 0;
    
    //順序查找
    int i = 0;
    for(i = 0; i < L.length; i++) {
        if (L.data[i] == e) {break;}
    }
    
    if (i >= L.length) return 0;
    
    return i;
}

// 10. 單元測試
void test() {
    KSList L1;
    //KSList L2;
    KElementType e;
    KStatus iStatus;
    
    //1.1 順序表初始化
    iStatus = initSequenceList(&L1);
    printf("初始化L1後: L.Length = %d\n", L1.length);
    
    //1.2 順序表數據插入
    for(int j=1; j <= 5;j++){
        iStatus = insertElement(&L1, 1, j);
    }
    printf("插入數據L1長度: %d\n",L1.length);
    
    //1.3 順序表取值
    getElement(L1, 5, &e);
    printf("順序表L1第5個元素的值爲:%d\n",e);
    
    //1.4 順序表刪除第2個元素
    deleteElement(&L1, 2);
    printf("順序表L1刪除第%d元素,長度爲%d\n",2,L1.length);
    
    //1.5 清空順序表
    iStatus = clearList(&L1);
    printf("清空L1後,L.length = %d\n",L1.length);
    
    //1.6 判斷List是否爲空
    iStatus = isListEmpty(L1);
    printf("L1是否空:i=%d(1:是 0:否)\n",iStatus);
    
    //1.8 遍歷打印順序表
    for(int j=1; j <= 5;j++){
        iStatus = insertElement(&L1, 1, j);
    }
    traverseList(L1);
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    test();
    return 0;
}

複製代碼

1.3.2 線性表之單鏈表

單鏈表Demo點擊這裏下載:單鏈表操做

  • 單鏈表結點

單鏈表結點

//定義結點
typedef struct KNodeInfo{
    KElementType data;
    struct KNodeInfo *next; }Node; 複製代碼
  • 單鏈表邏輯狀態

    單鏈表邏輯狀態

  • 增長頭結點的單鏈表邏輯狀態

    增長頭結點的單鏈表邏輯狀態

  • 單鏈表爲何要增長頭結點

    單鏈表爲何要增長頭結點

1.3.2.1 單鏈表初始化

//1. 初始化單鏈表
KStatus initList(KLinkList *L) {
    
    //生成頭結點,並使用L指向此頭結點
    *L = (KLinkList)malloc(sizeof(Node));
    //若是分配空間失敗,直接退出
    if(*L == NULL) return ERROR;
    //將頭結點的指針域置爲空
    (*L)->next = NULL;
    
    return OK;
}
複製代碼

1.3.2.2 單鏈表插入結點

  • 單鏈表插入
    單鏈表插入
    實現代碼:
//2. 單鏈表插入
//初始條件:順序線性表L已存在,1≤i≤ListLength(L);
//操做結果:在L中第i個位置以後插入新的數據元素e,L的長度加1;
KStatus insertElement(KLinkList *L, int i, KElementType e) {
    
    int j = 1;
    KLinkList p, s;
    p = *L;
    
    //尋找第i-1個結點
    while (p && j < i) {
        p = p->next;
        ++j;
    }
    //判斷第i個元素是否存在
    if(!p || j > i) return ERROR;
    
    //生成新結點s
    s = (KLinkList)malloc(sizeof(Node));
    //將e賦值給s的數值域
    s->data = e;
    //將p的後繼結點賦值給s的後繼
    s->next = p->next;
    //將s賦值給p的後繼
    p->next = s;
    
    return OK;
}
複製代碼

1.3.2.3 單鏈表刪除結點

  • 單鏈表刪除
    單鏈表刪除

實現代碼:

//4. 單鏈表刪除元素
//初始條件:順序線性表L已存在,1≤i≤ListLength(L)
//操做結果:刪除L的第i個數據元素,並用e返回其值,L的長度減1
KStatus deleteElement(KLinkList *L, int i, KElementType *e) {
    
    int j = 1;
    KLinkList p,q;
    p = (*L)->next;
    
    //查找第i-1個結點,p指向該結點
    while (p->next && j < (i-1)) {
        p = p->next;
        ++j;
    }
    
    //當i>n 或者 i<i 時,刪除位置不合理
    if (!(p->next) || (j>i-1)) return ERROR;
    
    //q指向要刪除的結點
    q = p->next;
    //將q的後繼賦值給p的後繼
    p->next = q->next;
    //將q結點中的數據賦值給e
    *e = q->data;
    //釋放內存
    free(q);
    
    return OK;
}
複製代碼

1.3.2.4 單鏈表取值

//3. 單鏈表取值
//初始條件: 順序線性表L已存在,1≤i≤ListLength(L);
//操做結果:用e返回L中第i個數據元素的值
KStatus getElement(KLinkList L, int i, KElementType *e) {
    int j = 1;
    //將結點p,指向鏈表L的第一個結點
    KLinkList p = L->next;
    
    //查找結點,p不爲空,且計算j不等於i,則循環繼續
    while (p && j < i) {
        p = p->next;
        ++j;
    }
    //若是p爲空或者j>i,則 返回error
    if(!p || j > i) return ERROR;
    
    *e = p->data;
    
    return OK;
}
複製代碼

1.3.2.5 單鏈表建表-頭插法

  • 單鏈表前插法
    單鏈表前插法
    實現代碼:
//7. 建立單鏈表:頭插入法
//隨機產生n個元素值,創建帶表頭結點的單鏈線性表L(前插法)
void createListByHeadInsert(KLinkList *L, int n) {
    KLinkList p;
    //創建1個帶頭結點的單鏈表
    *L = (KLinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    
    //循環前插入隨機數據
    for (int i = 0; i < n; i++) {
        //生成新結點
        p = (KLinkList)malloc(sizeof(Node));
        //i賦值給新結點
        p->data = i;
        
        //將結點p插入到頭結點以後
        p->next = (*L)->next;
        (*L)->next = p;
    }
}
複製代碼

1.3.2.6 單鏈表建表-頭插法

  • 單鏈表後插法
    單鏈表後插法
    實現代碼:
//8. 建立單鏈表:尾部插入法
//隨機產生n個元素值,創建帶表頭結點的單鏈線性表L(後插法)
void createListByTailInsert(KLinkList *L, int n) {
    
    KLinkList p, r;
    
    //創建1個帶頭結點的單鏈表
    *L = (KLinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    
    //讓r指針指向尾部結點
    r = *L;
    
    //循環建立鏈表結點,尾部插入
    for (int i = 0; i < n; i++) {
        //生成新結點
        p = (Node *) malloc(sizeof(Node));
        //賦值隨機數i
        p->data = i;
        
        //尾部插入新結點
        //將表尾終端結點的指針指向新結點
        r->next = p;
        //將當前的新結點定義爲表尾終端結點
        r = p;
    }
    
    //將尾部指針next=NULL
    r->next = NULL;
    
}

複製代碼

1.3.2.7 遍歷單鏈表

//5. 遍歷單鏈表
//初始條件:順序線性表L已存在
//操做結果:依次對L的每一個數據元素輸出
KStatus traverseList(KLinkList L) {
    KLinkList p = L->next;
    while (p) {
        printf("%d\n",p->data);
        p = p->next;
    }
    printf("\n");
    return OK;
}
複製代碼

1.3.2.8 清空單鏈表

//6. 清空單鏈表
//初始條件:順序線性表L已存在。操做結果:將L重置爲空表
KStatus clearList(KLinkList *L) {
    
    KLinkList p, q;
    //指向第一個結點
    p = (*L)->next;
    while (p) {
        //遍歷刪除每一個結點,並釋放內存
        q = p->next;
        free(p);
        p = q;
    }
    //頭結點指針域賦值爲空
    (*L)->next = NULL;
    
    return OK;
}
複製代碼

1.3.2.9 單鏈表操做完整代碼

//
// main.c
// 003_LInkedStorage
//
// Created by 孔雨露 on 2020/4/5.
// Copyright © 2020 Apple. All rights reserved.
//

#include <stdio.h>
#include "string.h"
#include "ctype.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"

#define ERROR 0
#define TRUE 0
#define FALSE 0
#define OK 1

#define MAXSIZE 20

typedef int KStatus;
typedef int KElementType;

//定義結點
typedef struct KNodeInfo{
    KElementType data;
    struct KNodeInfo *next; }Node; typedef struct KNodeInfo *KLinkList; //1. 初始化單鏈表 KStatus initList(KLinkList *L) {
    
    //生成頭結點,並使用L指向此頭結點
    *L = (KLinkList)malloc(sizeof(Node));
    //若是分配空間失敗,直接退出
    if(*L == NULL) return ERROR;
    //將頭結點的指針域置爲空
    (*L)->next = NULL;
    
    return OK;
}

//2. 單鏈表插入
//初始條件:順序線性表L已存在,1≤i≤ListLength(L);
//操做結果:在L中第i個位置以後插入新的數據元素e,L的長度加1;
KStatus insertElement(KLinkList *L, int i, KElementType e) {
    
    int j = 1;
    KLinkList p, s;
    p = *L;
    
    //尋找第i-1個結點
    while (p && j < i) {
        p = p->next;
        ++j;
    }
    //判斷第i個元素是否存在
    if(!p || j > i) return ERROR;
    
    //生成新結點s
    s = (KLinkList)malloc(sizeof(Node));
    //將e賦值給s的數值域
    s->data = e;
    //將p的後繼結點賦值給s的後繼
    s->next = p->next;
    //將s賦值給p的後繼
    p->next = s;
    
    return OK;
}

//3. 單鏈表取值
//初始條件: 順序線性表L已存在,1≤i≤ListLength(L);
//操做結果:用e返回L中第i個數據元素的值
KStatus getElement(KLinkList L, int i, KElementType *e) {
    int j = 1;
    //將結點p,指向鏈表L的第一個結點
    KLinkList p = L->next;
    
    //查找結點,p不爲空,且計算j不等於i,則循環繼續
    while (p && j < i) {
        p = p->next;
        ++j;
    }
    //若是p爲空或者j>i,則 返回error
    if(!p || j > i) return ERROR;
    
    *e = p->data;
    
    return OK;
}

//4. 單鏈表刪除元素
//初始條件:順序線性表L已存在,1≤i≤ListLength(L)
//操做結果:刪除L的第i個數據元素,並用e返回其值,L的長度減1
KStatus deleteElement(KLinkList *L, int i, KElementType *e) {
    
    int j = 1;
    KLinkList p,q;
    p = (*L)->next;
    
    //查找第i-1個結點,p指向該結點
    while (p->next && j < (i-1)) {
        p = p->next;
        ++j;
    }
    
    //當i>n 或者 i<i 時,刪除位置不合理
    if (!(p->next) || (j>i-1)) return ERROR;
    
    //q指向要刪除的結點
    q = p->next;
    //將q的後繼賦值給p的後繼
    p->next = q->next;
    //將q結點中的數據賦值給e
    *e = q->data;
    //釋放內存
    free(q);
    
    return OK;
}

//5. 遍歷單鏈表
//初始條件:順序線性表L已存在
//操做結果:依次對L的每一個數據元素輸出
KStatus traverseList(KLinkList L) {
    KLinkList p = L->next;
    while (p) {
        printf("%d\n",p->data);
        p = p->next;
    }
    printf("\n");
    return OK;
}

//6. 清空單鏈表
//初始條件:順序線性表L已存在。操做結果:將L重置爲空表
KStatus clearList(KLinkList *L) {
    
    KLinkList p, q;
    //指向第一個結點
    p = (*L)->next;
    while (p) {
        //遍歷刪除每一個結點,並釋放內存
        q = p->next;
        free(p);
        p = q;
    }
    //頭結點指針域賦值爲空
    (*L)->next = NULL;
    
    return OK;
}

//7. 建立單鏈表:頭插入法
//隨機產生n個元素值,創建帶表頭結點的單鏈線性表L(前插法)
void createListByHeadInsert(KLinkList *L, int n) {
    KLinkList p;
    //創建1個帶頭結點的單鏈表
    *L = (KLinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    
    //循環前插入隨機數據
    for (int i = 0; i < n; i++) {
        //生成新結點
        p = (KLinkList)malloc(sizeof(Node));
        //i賦值給新結點
        p->data = i;
        
        //將結點p插入到頭結點以後
        p->next = (*L)->next;
        (*L)->next = p;
    }
}

//8. 建立單鏈表:尾部插入法
//隨機產生n個元素值,創建帶表頭結點的單鏈線性表L(後插法)
void createListByTailInsert(KLinkList *L, int n) {
    
    KLinkList p, r;
    
    //創建1個帶頭結點的單鏈表
    *L = (KLinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    
    //讓r指針指向尾部結點
    r = *L;
    
    //循環建立鏈表結點,尾部插入
    for (int i = 0; i < n; i++) {
        //生成新結點
        p = (Node *) malloc(sizeof(Node));
        //賦值隨機數i
        p->data = i;
        
        //尾部插入新結點
        //將表尾終端結點的指針指向新結點
        r->next = p;
        //將當前的新結點定義爲表尾終端結點
        r = p;
    }
    
    //將尾部指針next=NULL
    r->next = NULL;
    
}

//9. 單元測試
void test() {
        KStatus iStatus;
        KLinkList L;
        KElementType e;
        
        //2.1 單鏈表初始化
        iStatus = initList(&L);
        printf("L 是否初始化成功?(0:失敗,1:成功) %d\n",iStatus);
        
        //2.2 單鏈表插入數據
        for(int j = 1;j<=10;j++)
        {
            iStatus = insertElement(&L, 1, j);
        }
        printf("L 插入後\n");
        traverseList(L);
        
        //2.3 單鏈表獲取元素
        getElement(L,5,&e);
        printf("第5個元素的值爲:%d\n",e);
        
        //2.4 刪除第5個元素
        iStatus = deleteElement(&L, 5, &e);
        printf("刪除第5個元素值爲:%d\n",e);
        traverseList(L);
        
        //3.1 前插法整理建立鏈表L
        iStatus = clearList(&L);
        createListByHeadInsert(&L, 20);
        printf("整理建立L的元素(前插法):\n");
        traverseList(L);
        
        //3.2 後插法整理建立鏈表L
        iStatus = clearList(&L);
        createListByTailInsert(&L, 20);
        printf("整理建立L的元素(後插法):\n");
        traverseList(L);
}


int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    test();
    return 0;
}

複製代碼

相關文章
相關標籤/搜索