1. 程式人生 > >資料結構(學習中)

資料結構(學習中)

預先知識:(C語言)

1、指標

地址:記憶體單元的編號

指標:指標就是地址,地址就是指標

           指標變數:就是一個變數,這個變數儲存了一個非負整數,即儲存了記憶體單元的編號的變數,所有指標變數只佔4個位元組(32位機器來說)

基本型別的指標

指標和陣列

#include <stdio.h>

int main(void)
{
    int a[5] = {1, 2, 3, 4, 5};
    
    int *p = a; 
    /* int *p; p = a 
        陣列名也是一個變數,一個指向陣列第一個元素的指標變數,存放了第一個元素的記憶體空間地址
        將陣列名給指標變數p,意味著p指向陣列的第一個元素(真正指向第一個元素的第一個位元組單元,因為這是個整型指標變數,所以可以理解為指向第一個元素)
        a = p => 第一個元素的記憶體空間地址
        所以 *p就是第一個元素的記憶體空間 *(p+1) 是第二個元素的記憶體空間,因為p = a,所以a[0] = p[0]
    */

    printf("p = %p\n", p);
    
    printf("*p = %d\n", *p);
    
    printf("p[0] = %d\n", p[0]);
    
    printf("*(p+1) = %d\n", *(p+1));
    
    printf("*p+1 = %d\n", (*p + 1) );
    
    return 0;
}

傳遞給被調函式的實參,在被調函式中修改後,在主調函式中能響應到 ,這裡可以說是主調函式中的靜態記憶體在被調函式中進行了修改,關鍵在於:在主調函式中的實參是否想要在被調函式中被修改,如果想被修改,就需要傳實參地址給被調函式

#include <stdio.h>

void func(int *);

int main(void)
{
    int p = 1;
    func(&p);   
    /* 主調函式中要使用取地址符&來獲取整型p(對4個位元組的記憶體空間命名為p,沒有其他實際意義)的第一個位元組地址,傳遞到func中 
    */
    printf("p = %d\n",p);
}

void func(int *pt)
{
    *pt = 10; 
    /* pt是一個整型指標變數,雖然它指向p的第一個位元組地址 ,但對剩餘的3個位元組也有訪問許可權, 所以*pt應該是代表了4個位元組單元空間 即*pt = p,這樣對記憶體進行操作了,主調函式中的變數自然就會被改變
    */
}

2、結構體

1、定義的末位分號不能省

2、資料型別是 【struct 名稱】整體是一個數據型別

3、‘.’成員操作符,結構體變數.成員;‘->’成員操作符,結構體指標變數->成員

4、與基本資料型別不同,不能算術運算和邏輯運算,允許賦值操作

#include <stdio.h>

struct ST1
{
    char name[200] ;
    int age;
};  //末位的;不能省略

int main(void)
{
    struct ST1 st = {"zhangsan", 20};
    struct ST1 st2 = {"wangwu", 22};
    struct ST1 *sp ;
    sp = &st;

    //定義結構體變數,使用'.'成員操作符獲取結構體的成員變數值
    printf("name = %s\n", st.name);
    printf("age = %d\n", st.age);

    //定義結構體指標變數,使用'->'成員操作符獲取結構體的成員變數值
    printf("name = %s\n", sp->name);
    printf("age = %d\n", sp->age);

    /*   
        sp = &st,sp儲存了結構體變數st的地址,這個地址是結構體的第一個位元組的記憶體空間的地址
        *sp 就是結構體的位元組空間(準確來講是結構體第一個位元組空間,因為sp是這個結構體的指標,所以後面空間也能被方訪問),所以 *sp = st  ===> (*sp).name = st.name  ,為了書寫方便,
        就把(*sp).name 替換成了 sp->name,這個是C語言開發人員人為約定的。
    */

    // st + st2 這個操作是不允許的,同樣減法,乘法,除法都不能操作,只允許賦值
    printf("name = %s\n", st2.name);
    printf("age = %d\n", st2.age);

    st2 = st;
    printf("name = %s\n", st2.name);
    printf("age = %d\n", st2.age);

    return 0;
}

3、動態記憶體分配和釋放

靜態記憶體分配:由申明好的資料型別,系統自動分配指定好的型別記憶體分配數量,這個可以在被調函式中修改它的記憶體值

malloc() free()

跨函式使用記憶體,只能通過動態分配記憶體才能保證記憶體被函式外使用,什麼時候釋放,取決於什麼時候使用free函式

在被呼叫函式中國動態分配記憶體,供主調函式進行使用和修改

#include <stdio.h>
#include <malloc.h>

int declare_memory(int *p , int memory_size);

int main(void)
{
    int * p;  //這個整型指標用於指向申請的記憶體空間的地址

    /* 
        使用&為了能夠在主調函式中響應修改後實參的值
        p是 int *型別
        &p 就是 int **型別了,所以被調函式中形參的型別也要對應
        如果使用declare_memory(p, 12),而被調函式的形參使用int *型別,這樣和一般函式的實參修改        一樣,不能將被調函式修改後的值傳遞到主調函式中了
    */
    if(!declare_memory(&p, 12))  
    {
        printf("記憶體申請失敗");
        return 0;
    }

    //在這裡釋放declare_memory函式申請的記憶體空間,如果一直不釋放,要麼等到main函式執行結束,要        麼就會記憶體洩漏,
    free(p);
    
    return 0;
}

int declare_memory(int **p , int memory_size)
{
    // 這裡的*p == 主調函式中的p了
    *p = (int *)malloc(sizeof(int)*memory_size);
    if(*p == NULL)
    {
        return 0;
    }
    return 1;
}

資料結構:

將現實中大量而複雜的問題轉換為資料,儲存到記憶體中,這個就是資料結構,以及在此基礎上實現某些功能而執行的相應的操作,這個操作就是演算法

演算法:

狹義的演算法與資料的存取方式密切相關

廣義的演算法與資料的儲存無關

泛型:利用某種技術達到這個效果(不同的資料儲存方式,執行的操作是一樣的)

時間複雜度和空間複雜度

程式 = 資料的儲存 + 資料的操作 + 可以被計算機執行的語言

 

1、線性結構:第一個元素只有後繼元素,最後一個元素只有前驅元素,其餘元素都有前驅元素和後繼元素

基本操作:

1、初始化  int init(&L)

2、銷燬    int destroy(&L)

3、重置    int clear(&L)

4、判空    bool isEmpty(&L)

5、獲取已存元素數量 void listLen(&L,int * len)

6、獲取指定索引的元素 int getElem(&L, int index,ElemType *e)

7、獲取給定值並滿足給定關係的第一個元素(這裡的一個形參是函式型別) int locateElem(&L, ElemType elem, (int)(*comp)(ElemType , ElemType) )

8、獲取一個元素的前驅 int getPriorElem(&L, ElemType elem, ElemType *pre_)

9、獲取一個元素的後繼 int getNextElem(&L, ElemType elem , ElemType *next_)

10、插入一個元素 int insertElem(&L, int index, ElemType elem)

11、刪除一個元素 int deleteElem(&L, int index, ElemType *del_elem)

12、根據給定關係改變元素的值(這裡同樣有一個形參是函式型別)int traverse(&L, (void)(*update)(ElemType *))

連續儲存:陣列 ,操作程式碼

離散儲存:連結串列,操作程式碼迴圈連結串列程式碼(待更新)

        首節點(第一個存有有效元素的結點),尾節點(最後一個存有有效元素的結點),頭結點(首結點前的一個結點),頭指標(指向頭結點的指標),尾指標(指向尾結點的指標)

一般連結串列結構圖

線性結構的常見應用:棧和佇列

結構如下,

typedef int ElemType;

typedef struct Node
{
    ElemType data;
    struct Node *next;
}Node,*PNode;

typedef struct stack
{
    PNode base; //棧基地址,棧底指標
    PNode pop;  //棧頂指標
    int len;  //棧長度,方便操作
}STACK;

typedef struct queue
{
    PNode front;
    PNode rear;
}QUEUE;

棧:只允許在棧頂插入元素和刪除元素 ,鏈棧程式碼順序棧程式碼(待更新)

佇列:只允許在佇列頭刪除元素,在佇列尾插入元素,靜態佇列程式碼(待更新),鏈式佇列(待更新)

        靜態佇列一般是迴圈佇列,容易操作,入隊就把隊尾向上加,出隊把隊頭向上加,如果隊頭或隊尾到達空間上限,就置0,一是為了不浪費空間,二是不用將佇列元素整體向上或向下移動

        鏈式佇列就和一般連結串列操作相同,只是只能在head結點加入,在tail結點刪除

鏈式結構和順序結構除了操作細節不一樣之外,廣義上說是完全一樣的結構型別

2、非線性結構

二叉數:前序遍歷(根,左,右),中序遍歷(左,根,右),後序遍歷(左,右,根),層次遍歷(從根向下,一層一層的,每層從左到右)

3、查詢和排序

折半查詢

排序:

        冒泡

        插入

        選擇

        歸併

        快速