數據結構(一)--入門和預備知識

2018-12-27

1. 概述


數據結構定義:


我們如何把現實中大量而複雜的問題以特定的數據類型和特定的存儲結構保存到主存儲器(内存)中,以及在此基礎上爲實現某個功能(néng)(如元素的CURD、排序等)而執行的相應操作,


這(zhè)個相應的操作也叫(jiào)算法。


數據結構 = 元素 + 元素的關系


算法 = 對(duì)數據結構的操作


算法:


算法就是:解決問題的方法和步驟


衡量算法有如下标準:


1.時間複雜度


程序要執行的次數,并非執行時間


2.空間複雜度


算法執行過(guò)程中大概要占用的最大内存


3.難易程度(可讀性)


4.健壯性


2. 數據結構的特點和地位


地位:


數據結構處于軟件中核心的地位。


如計算機内存中棧和堆的區别,不懂數據結構的人可能(néng)會認爲内存就是分兩(liǎng)大部分,一塊叫(jiào)棧,一塊叫(jiào)堆,顯然這(zhè)是非常膚淺且不正确的結論。


實際上如果一塊内存是以壓棧出棧的方式分配的内存,那麼(me)這(zhè)塊内存就叫(jiào)棧内存,如果是以堆排序的方式分配的内存,那麼(me)這(zhè)塊内存就叫(jiào)堆内存,其最根本的區别還(hái)是其内存分配


算法的不同。


例如,函數的調用方式也是通過(guò)壓棧出棧的方式來調用的,或者操作系統中多線程操作有隊列的概念,隊列用于保證多線程的操作順序,這(zhè)也是數據結構裡(lǐ)面(miàn)的内容、或者計算機


編譯原理裡(lǐ)面(miàn)有語法樹的概念,這(zhè)實際上就是數據結構裡(lǐ)面(miàn)的樹,比如軟件工程、數據庫之類都(dōu)有數據結構的影子。


特點:


數據結構修煉的是内功,并不能(néng)直接立竿見影的可以解決現實問題,但是有了這(zhè)門内功會在其他方面(miàn)的學(xué)習中對(duì)你大有益處。


3. 預備知識(C語言)


學(xué)習數據結構應該具備如下知識:


指針


結構體


動态内存的分配和釋放


跨函數使用内存


本小節主要介紹學(xué)習數據結構應該有的基礎,并對(duì)相關知識稍作講解。


指針


指針是 C語言 的靈魂,重要性不需多言。


指針定義


地址:


地址是内存單元的編号


其編号是從 0 開(kāi)始的非負整數


範圍: 0 -- 0xFFFFFFFF (2^32 - 1)  注:此指x86平台,x64平台下最大内存地址爲 (2^64 - 1)


指針:


指針就是地址,地址就是指針。


指針變量是存放内存單元地址的變量,它内部保存的值是對(duì)應的地址,地址就是内存單元的編号(如内存地址值:0xffc0)。


指針的本質是一個操作受限的非負整數

  

在計算機系統中,CPU 可以直接操作内存,關于 CPU 對(duì)内存的操作與控制原理可以簡單理解如下圖


微信圖片_20181227145826.jpg


地址線 : 确定操作哪個地址單元


控制線 : 控制該數據單元的讀寫屬性


數據線 : 傳輸 CPU 和内存之間的數據


指針的分類


1.基本類型的指針


int i = 10;  // 定義一個 整形變量 i 初始值 10
int *p = i;  // 定義一個 整形的指針變量 p , 變量 p 指向(xiàng) i 的地址    
int *p;      // 這(zhè)兩(liǎng)行等價于上面(miàn)一行
p = &i;
1. p 存放了 i 的地址,我們就可以說“ p 指向(xiàng)了 i” ,但 p 和 i 是兩(liǎng)個不同的變量,修改一方不會影響另一個的值。
2. *p 等價于 i ,i 等價于 *p;兩(liǎng)者是一塊内存空間,裡(lǐ)面(miàn)的值一變具變。


2.指針和函數


// 修改外部實參的值
void func(int * p)
{
    *p = 100;   // 函數内修改外部變量的值   
}
// 修改外部實參的值,二級指針的值
void func2(int ** p)
{
    *p = 100;   
    // 函數内修改外部變量的值 ,這(zhè)裡(lǐ)實際修改的是指針的内部的地址,這(zhè)裡(lǐ)直接自己修改并不嚴謹也不安全,隻是爲了表達意思  
}
int main(void)
{
    // 修改外部實參
   int i = 10;
   func(&i);
   printf("i = %d",i);
   // 修改外部二級指針
   int *p = i; // 等價于 int *p; p = &i;
   func(&p);
   printf("i = %d",i);
   return 0;
}
// 通過(guò)函數調用,改變函數外部變量(實參)的值


3.指針和數組


【指針】 和 【一維數組】


int a[5] = {1,2,3,4,5 };
 a[3] == *(a + 3)  
 // 等價于 a[3] == *(3 + a) == 3[a];
 // 3[a] 這(zhè)種(zhǒng)寫法隻是不常用,從原理上來說是正确的 a 等價于 a[0];
 // a 是數組中第一個元素,每個元素占用内存單位長(cháng)度相同,
 // a[i] 中的 i 實際代表的是單位長(cháng)度的倍數


數組名:


 一維數組名是個指針常量(它的值不可變)


它存放的是該一維數組的第一個元素的地址(一維數組名指向(xiàng)其第一個元素)


下标和指針的關系:


(1)、 a[i] 等價于 *(a + i)


(2)、假設指針變量的名字爲 p,


則 p + i 的值爲 p + i * (p 所指向(xiàng)的變量所占字節數)


(3)、每個下标表示的是第 i+1 個元素,根據元素類型分配的字節長(cháng)度不同(int 類型4個字節),每個字節都(dōu)有對(duì)應的内存地址編号,指針變量 p 保存的是該元素首字節的地址。


指針變量的運算: 


指針變量不能(néng)相加、相乘、相除


如果兩(liǎng)指針變量屬于同一數組,則可以相減


指針變量可以加減一個整數,前提是最終結果不能(néng)超過(guò)指針最大可訪問範圍


// 指針變量的運算
p + i 的值是 p + i*(所指向(xiàng)的變量所占字節數)
p - i 的值是 p - i*(所指向(xiàng)的變量所占字節數)
p++   等價于 p + 1
p--   等價于 p - 1
// 下面(miàn)是一個通過(guò)函數修改數組内部元素
void my_Array(int *a , int length)
{
    for(int i = 0; i < length; i++)
    {
        *a[i]++;  // 給每個元素加 1
    }
}
int main(void){
    int a[5] = {1,2,3,4,5};
    my_Array(a , 5); // 調用
}


結構體


爲什麼(me)會出現結構體、


爲了表示一些複雜的數據,而普通的基本數據無法滿足要求.


什麼(me)叫(jiào)結構體


結構體是用戶根據實際需要,自己定義的複合數據類型


// 如學(xué)生類型
struct Student{
   int age;
   char * name; // name 不同,賦值方法不同
   char name2[100]; // 這(zhè)個隻能(néng) strcpy(s.name2, "zhangwangdsd"); 字符串拷貝
   double height;
};


如何使用結構體


總結起(qǐ)來有兩(liǎng)種(zhǒng)結構體的使用方式:直接使用 && 通過(guò)指針使用


struct Student ss = {12,"xiaoyou",1.73,"xiaozhang"};

struct Student *pst = &ss;


ss.name ;  這(zhè)裡(lǐ)直接操作結構體本身


pst -> name ;   這(zhè)裡(lǐ)通過(guò)指針地址操作,更加節省空間


struct Student{ // 自定義結構體
    int age;
    char * name;
    double height;
    char name2[100];
};
int main(void) {
   struct Student s = {12,"xiaoyou",1.73,"xiaozhang"};
// 直接使用
   printf(" age = %d 
 name = %s 
 height = %.2f 
",s.age,s.name,s.height);
   s.age = 21;
   s.name = "xiaozhu";
   strcpy(s.name2, "zhangwangdsd");  // 字符串拷貝
   s.height = 1.70;
   printf(" age = %d 
 name = %s 
 height = %.2f 
 %s 
",s.age,s.name,s.height,s.name2);
   // 以指針的方式使用
   struct Student *pst = &ss;
   pst -> name = "my new name";
   printf(" name = %s
",pst->name);
   printf(" name = %s
",(*pst)->name);
// pst -> name 等價于 (*pst).name ,
// 而(*pst).name 又等價于 ss.name
// 所以 pst -> name 等價于 ss.name
   return 0;
}
注意事(shì)項


結構體變量的類型爲: struct Student


結構體變量不能(néng)加減乘除,但是能(néng)夠相互賦值


普通結構體變量和結構體指針變量作爲函數傳參的問題


typedef struct Student{ // 結構體定義
    int age;
    char * name;
    char name2[100];
    double height;
}myStudent;
// 直接傳遞整個結構體數據,耗時 && 浪費内存空間
void func(struct Student st);
// 直接傳遞 隻占用 4 byte 的指針,省時效率也高 <推薦用法>
void func2(struct Student * pst);
int main(void){
    myStudent ss = {12,"xiaoyou",1.73};
    func(ss);
    func2(&ss);
    return 0;
}
void func(struct Student st){
    printf("age = %d 
 name = %s",st.age,st.name);
}
void func2(struct Student * pst){
    printf("age = %d 
 name = %s",(*pst).age,(*pst).name);
    printf("age = %d 
 name = %s",pst->age,pst->name);
}


動态内存分配和釋放


平時直接創建數組的寫法都(dōu)是靜态創建,創建完畢之後(hòu)在整個程序的運行過(guò)程中,會固定占用對(duì)應的内存,不僅會造成(chéng)内存空間浪費,還(hái)無法動态添加元素,所以局限性很大,而


程序中我們爲了避免這(zhè)種(zhǒng)情況,應該使用動态的方式創建和銷毀數組。


// 靜态創建數組
int a[5] = {1,2,3,4,5};


動态構造一維數組


動态構造一個 int 型的一維數組。


int *p = (int *)malloc(int length);
1. void * malloc(size_t __size) 函數,隻有一個 int 類型的形參,表示要求系統分配的字節數
2. malloc 函數的功能(néng)是請求系統 length 個字節的内存空間,如果請求完成(chéng)則返回的是第一個字節的地址,
    如果請求不成(chéng)功,則返回NULL
3. malloc 函數能(néng)且隻能(néng)返回第一個字節的地址,所以我們需要把沒(méi)有實際意義的第一個字節地址(幹地址)轉化爲一個有實際意義的地址,
    所以 malloc 前面(miàn)必須加(數據類型 *),表示把這(zhè)個無意義的地址轉化爲對(duì)應類型的地址
    實例:
        int *p = (int *)malloc(50);
        表示將(jiāng)系統分配的 50 個字節的第一個字節的地址轉化爲 int 類型的地址,準确的說是轉化爲 4 個一組的地址的首地址,
        這(zhè)樣 p 就指向(xiàng)了第一個四個字節··· p+i 就指向(xiàng)了第 i+1 個四個字節,p[0],p[i]也就分别是第一個,第i+1個元素。
        double *p = (double *)malloc(80);
        表示將(jiāng)系統分配的 80 個字節的第一個字節的地址轉化爲 double 類型的地址,準确的說是轉化爲 8 個一組的地址的首地址,
        這(zhè)樣 p 就指向(xiàng)了第一個八個字節··· p+i 就指向(xiàng)了第 i+1 個八個字節,p[0],p[i]也就分别是第一個,第i+1個元素。
4. free(p);
    釋放 p 所指向(xiàng)的内存,而不是釋放 p 本身所占用的内存


代碼示例如下:


void test2(void)
{
    int len;
    printf("請輸入你要動态創建的數組長(cháng)度:");
    scanf("%d",&len);
    int *pArr = (int *)malloc(len); // 動态創建數組
    *pArr = 4;      // 相當于 a[0] = 4;  這(zhè)裡(lǐ) pArr 就等于數組首地址,等于數組名
    pArr[2] = 5;    // 相當于 a[2] = 5;
    printf("pArr[0] = %d 
pArr[2] = %d
",pArr[0],pArr[2]);
    free(pArr);     // 使用完畢,釋放對(duì)應的數組空間 
}


跨函數使用内存


在函數内部分配的局部變量,在函數調用完成(chéng)之後(hòu)就會被系統回收,其内存也會消失。但是程序中常常需要定義一塊内存,當我們用完之後(hòu)再會回收。如 OC 語言中對(duì)象。所以需


要保存住分配的内存,應該用動态分配内存,當用完之後(hòu)再手動釋放。這(zhè)也是C語言的一個不足之處:内存需要我們手動創建和手動釋放,這(zhè)也是 OC 語言在開(kāi)發(fā) iOS 程序時候,


我們所講的MRC。【蘋果也發(fā)現了這(zhè)個不足,于 iOS 5 的時候推出了ARC 】


下面(miàn)是一個跨函數使用内存的例子:


// 這(zhè)個例子已經(jīng)非常有面(miàn)向(xiàng)對(duì)象的味道(dào)了
typedef struct Student{     // 自定義 student 結構體
    int age;
    char * name;
}myStudent;
myStudent * createStudent(void); // 創建 student 
void showStudent(myStudent *);   // 輸出 student
int main(void) {
    myStudent *p = createStudent();  // 創建 student 
    showStudent(p);                  // 輸出 student
    return 0;
}
myStudent * createStudent(void)
{
    myStudent *p = (myStudent *)malloc(sizeof(myStudent));
    p->age = 20;
    p->name = "xiaoyou";
    return p;
}
void showStudent(myStudent *p)
{
    printf("student.age = %d 
student.name = %s
",p->age,p->name);
}


4. 小結


本文主要講解了數據結構的定義和簡介。


回顧了學(xué)習數據結構應該具備的一些 C語言 的基礎知識,如指針、結構體、和内存等。


後(hòu)面(miàn)會繼續開(kāi)始對(duì)數據結構的講解。


您的項目需求咨詢熱線:0760-88610046(國(guó)家高新技術企業)

*請認真填寫需求,我們會在24小時内與您取得聯系。