1.1數(shù)據(jù)結(jié)構(gòu)概述
數(shù)據(jù)結(jié)構(gòu)是數(shù)據(jù)元素的集合,它研究的是數(shù)據(jù)的邏輯結(jié)構(gòu)和數(shù)據(jù)的物理結(jié)構(gòu)以及它們之間的相互關(guān)系,并對(duì)這種結(jié)構(gòu)定義相適應(yīng)的運(yùn)算,設(shè)計(jì)出相應(yīng)的算法,并確保經(jīng)過(guò)這些運(yùn)算以后所得到的新結(jié)構(gòu)仍保持原來(lái)的結(jié)構(gòu)類(lèi)型。簡(jiǎn)而言之,數(shù)據(jù)結(jié)構(gòu)是相互之間存在一種或多種特定關(guān)系的數(shù)據(jù)元素的集合,即帶“結(jié)構(gòu)”的數(shù)據(jù)元素的集合。“結(jié)構(gòu)”就是指數(shù)據(jù)元素之間存在的關(guān)系,分為邏輯結(jié)構(gòu)和存儲(chǔ)結(jié)構(gòu)。
1.2數(shù)據(jù)結(jié)構(gòu)的基本概念
數(shù)據(jù):程序的操作對(duì)象,用于描述客觀事物,是所有能被輸入到計(jì)算機(jī)中,且能被計(jì)算機(jī)處理的符號(hào)的集合,包括數(shù)值,圖片,視頻等。
數(shù)據(jù)對(duì)象:性質(zhì)相同的數(shù)據(jù)元素的集合,是數(shù)據(jù)的子集(類(lèi)似于數(shù)組)。例如所有人的身份信息可以作為一個(gè)數(shù)據(jù)對(duì)象。
數(shù)據(jù)元素:是組成數(shù)據(jù)的,且有一定意義的基本單位,在計(jì)算機(jī)中通常作為整體處理. 也被稱作"記錄"。例如每一個(gè)人的身份信息可能就是一個(gè)數(shù)據(jù)元素。
數(shù)據(jù)項(xiàng):一個(gè)數(shù)據(jù)元素由若干數(shù)據(jù)項(xiàng)組成,是組成數(shù)據(jù)元素的最小單位。在身份信息中,有姓名、有身份證編號(hào),這樣的信息就是數(shù)據(jù)元素中的數(shù)據(jù)項(xiàng)。
數(shù)據(jù)結(jié)構(gòu):是數(shù)據(jù)的組織形式,數(shù)據(jù)元素之間存在的一種或多種特定關(guān)系的數(shù)據(jù)元素集合。
數(shù)據(jù)類(lèi)型:是一組值的集合和定義在該集合上的操作的總和。其中有原子類(lèi)型,原子就是不可再分割的意思,它是原子類(lèi)型值的集合和定義在該集合上的操作。例如在 C 語(yǔ)言中的 int、char、float 等都是原子類(lèi)型。除了原子類(lèi)型,還有結(jié)構(gòu)類(lèi)型,它是結(jié)構(gòu)的集合和定義在集合上的操作。結(jié)構(gòu)就是多個(gè)原子類(lèi)型值的組合,其中有 list、map、set 等。最后還有抽象數(shù)據(jù)類(lèi)型,它是數(shù)據(jù)模型以及定義在該數(shù)據(jù)模型上的操作,可以用一個(gè)三元組來(lái)表示,分別是數(shù)據(jù)對(duì)象、數(shù)據(jù)關(guān)系和相關(guān)的操作。對(duì)于抽象數(shù)據(jù)類(lèi)型,只考慮它的邏輯特性,具體的內(nèi)部實(shí)現(xiàn)是不考慮的。例如在生活中所有的人、汽車(chē)都可以把它抽象出來(lái)作為一種抽象數(shù)據(jù)類(lèi)型。
關(guān)系如下圖:

這是一張成績(jī)單,如果把這張成績(jī)單叫做一個(gè)數(shù)據(jù)對(duì)象的話,那么每一個(gè)人的姓名可以稱作為一個(gè)數(shù)據(jù)項(xiàng),而每一個(gè)人所有的信息是一個(gè)數(shù)據(jù)元素。這張表還有類(lèi)似于小明排在小紅前面這樣的關(guān)系,這樣的關(guān)系就是所說(shuō)的結(jié)構(gòu)。
代碼片段如下:
/*
數(shù)據(jù): 程序的操作對(duì)象,用于描述客觀事物.
數(shù)據(jù)的特點(diǎn): 1?? 可以輸入到計(jì)算機(jī) 2?? 可以被計(jì)算機(jī)處理
數(shù)據(jù)項(xiàng): 一個(gè)數(shù)據(jù)元素由若干數(shù)據(jù)項(xiàng)組成
數(shù)據(jù)元素: 組成數(shù)據(jù)的對(duì)象的基本單位
數(shù)據(jù)對(duì)象: 性質(zhì)相同的數(shù)據(jù)元素的集合(類(lèi)似于數(shù)組)
結(jié)構(gòu): 數(shù)據(jù)元素之間不是獨(dú)立的,存在特定的關(guān)系.這些關(guān)系即是結(jié)構(gòu);
數(shù)據(jù)結(jié)構(gòu):指的數(shù)據(jù)對(duì)象中的數(shù)據(jù)元素之間的關(guān)系
*/
#include <stdio.h>
//聲明一個(gè)結(jié)構(gòu)體類(lèi)型
struct Teacher{ //一種數(shù)據(jù)結(jié)構(gòu)
char *name; //數(shù)據(jù)項(xiàng)--名字
char *title; //數(shù)據(jù)項(xiàng)--職稱
int age; //數(shù)據(jù)項(xiàng)--年齡
};
int main(int argc, const char * argv[]) {
struct Teacher t1; //數(shù)據(jù)元素;
struct Teacher tArray[10]; //數(shù)據(jù)對(duì)象;
t1.age = 18; //數(shù)據(jù)項(xiàng)
t1.name = "CC"; //數(shù)據(jù)項(xiàng)
t1.title = "講師"; //數(shù)據(jù)項(xiàng)
printf("老師姓名:%s\n",t1.name);
printf("老師年齡:%d\n",t1.age);
printf("老師職稱:%s\n",t1.title);
return 0;
}
1.3邏輯結(jié)構(gòu)與物理結(jié)構(gòu)
根據(jù)視角不同,我們將數(shù)據(jù)結(jié)構(gòu)分為2種: 邏輯結(jié)構(gòu)與物理結(jié)構(gòu);
1.3.1邏輯結(jié)構(gòu)
邏輯結(jié)構(gòu)是指數(shù)據(jù)元素之間的邏輯關(guān)系,它更貼近于現(xiàn)實(shí),即從邏輯關(guān)系上來(lái)描述數(shù)據(jù)。它是獨(dú)立于計(jì)算機(jī)的,與計(jì)算機(jī)內(nèi)部如何存儲(chǔ)是無(wú)關(guān)的。
在邏輯結(jié)構(gòu)中,具體分為四種:線性結(jié)構(gòu)、集合、樹(shù)形結(jié)構(gòu)、圖狀結(jié)構(gòu)。其中,把集合、樹(shù)形結(jié)構(gòu)、圖狀結(jié)構(gòu)統(tǒng)稱為非線性結(jié)構(gòu)。

集合是指除了所有的元素均在一個(gè)集合之內(nèi),之外再無(wú)其他的關(guān)系。在數(shù)學(xué)中,所有整數(shù)就是一個(gè)集合,所有的小數(shù)也是一個(gè)集合。
2.線性結(jié)構(gòu)

線性結(jié)構(gòu)中的數(shù)據(jù)元素之間是一對(duì)一的關(guān)系,如線性表,棧,隊(duì)列,數(shù)組,字符串等。
3.樹(shù)形結(jié)構(gòu)

重要的非線性數(shù)據(jù)結(jié)構(gòu)。樹(shù)形數(shù)據(jù)結(jié)構(gòu)可以表示數(shù)據(jù)表素之間一對(duì)多的關(guān)系.樹(shù)型結(jié)構(gòu)中的數(shù)據(jù)元素之間存在一種一對(duì)多的層次關(guān)系。包括二叉樹(shù),B樹(shù),哈夫曼樹(shù),紅黑樹(shù)等
4.圖形結(jié)構(gòu)

也叫做網(wǎng)狀結(jié)構(gòu),它是多對(duì)多的關(guān)系。比如城市的交通網(wǎng)絡(luò),每一個(gè)城市都連接這其他的許多城市。
1.3.2物理結(jié)構(gòu)
物理結(jié)構(gòu)是指數(shù)據(jù)結(jié)構(gòu)在計(jì)算機(jī)內(nèi)的表示,也稱為存儲(chǔ)結(jié)構(gòu)。它既包括數(shù)據(jù)元素本身的表示,也包括數(shù)據(jù)元素之間關(guān)系的表示。數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)應(yīng)該正確反映數(shù)據(jù)元素之間的邏輯關(guān)系.這才是關(guān)鍵! 如何存儲(chǔ)數(shù)據(jù)元素之間的邏輯關(guān)系,是實(shí)現(xiàn)物理結(jié)構(gòu)的重點(diǎn)和難點(diǎn).
數(shù)據(jù)元素的存儲(chǔ)結(jié)構(gòu)也分為四種:順序存儲(chǔ)、鏈?zhǔn)酱鎯?chǔ)、索引存儲(chǔ)、散列存儲(chǔ)。

2.鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu):是把數(shù)據(jù)元素放在任意的存儲(chǔ)單元里,這組存儲(chǔ)單元可以是連續(xù)的,也可以是不連續(xù)的. 數(shù)據(jù)元素的存儲(chǔ)關(guān)系并不能反映邏輯關(guān)系,因此需要用一個(gè)指針存放數(shù)據(jù)元素的地址,這樣通過(guò)地址就可以找到相關(guān)關(guān)聯(lián)數(shù)據(jù)元素的位置.

3.索引存儲(chǔ)結(jié)構(gòu):索引存儲(chǔ)在內(nèi)存中不僅僅要存放每一個(gè)數(shù)據(jù)元素,還要建立一張索引表。在索引表中有一個(gè)個(gè)索引項(xiàng),每一個(gè)索引項(xiàng)都存放兩個(gè)信息,一個(gè)是關(guān)鍵字,另一個(gè)是該關(guān)鍵字查找數(shù)據(jù)對(duì)應(yīng)的地址。一個(gè)個(gè)索引項(xiàng)可以非??焖俚恼业綄?duì)應(yīng)存儲(chǔ)數(shù)據(jù)的地址。
但是,索引存儲(chǔ)有一個(gè)缺點(diǎn),就是既要存放數(shù)據(jù)元素,又要存放索引表,在存放索引表的時(shí)候會(huì)消耗內(nèi)存資源。
4.散列存儲(chǔ):也稱為哈希存儲(chǔ),它是通過(guò)關(guān)鍵字的相應(yīng)函數(shù)運(yùn)算直接求得對(duì)應(yīng)數(shù)據(jù)元素的地址,它的查找速度也是非常快的。
總結(jié) 邏輯結(jié)構(gòu)是面向問(wèn)題的,而物理結(jié)構(gòu)就是面向計(jì)算機(jī)的. 其基本的目標(biāo)就是將數(shù)據(jù)以及邏輯關(guān)系存儲(chǔ)到計(jì)算機(jī)的內(nèi)存中.
2算法
對(duì)特定問(wèn)題求解步驟的一種描述,它是指令的有限序列,其中的每條指令表示一個(gè)或多個(gè)操作。
1.數(shù)據(jù)結(jié)構(gòu)和算法的關(guān)系
兩者基友聯(lián)系又有區(qū)別。聯(lián)系是程序=算法+數(shù)據(jù)結(jié)構(gòu)。數(shù)據(jù)結(jié)構(gòu)是算法實(shí)現(xiàn)的基礎(chǔ),算法總是要依賴某種數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)的。算法的操作對(duì)象是數(shù)據(jù)結(jié)構(gòu)。區(qū)別是數(shù)據(jù)結(jié)構(gòu)關(guān)注的是數(shù)據(jù)的邏輯結(jié)構(gòu)、存儲(chǔ)結(jié)構(gòu)有一集基本操作,而算法更多的是關(guān)注如何在數(shù)據(jù)結(jié)構(gòu)的基本上解決實(shí)際問(wèn)題。算法是編程思想,數(shù)據(jù)結(jié)構(gòu)則是這些思想的基礎(chǔ)。

2.算法的特性
有窮性:一個(gè)算法必須在執(zhí)行有窮步之后結(jié)束,且每一步都必須在有窮時(shí)間內(nèi)完成。如果有類(lèi)似無(wú)限循環(huán)的語(yǔ)句,那么就不能稱之為算法。
可行性:一個(gè)算法是可行的,即算法中描述的操作都是可以通過(guò)已經(jīng)實(shí)現(xiàn)的基本運(yùn)算執(zhí)行有限次來(lái)實(shí)現(xiàn)的。每一步操作都是可以實(shí)現(xiàn)的才能稱之為算法。
確定性:算法中每條指令、每條語(yǔ)句必須有確切的含義,相同的輸入必須得出到相同的輸出,不能產(chǎn)生二義性。
輸入:一個(gè)算法必須有零個(gè)或多個(gè)輸入。
輸出:一個(gè)算法必須有一個(gè)或多個(gè)輸出。
2.1算法的效率
1.正確性:應(yīng)該能夠正確的解決問(wèn)題。
2.可讀性:算法應(yīng)具有良好的可讀性,以幫助人們理解。在人們修改閱讀算法時(shí),人們應(yīng)能夠快速的理解掌握該算法。
3.健壯性:健壯性是指在輸入非法數(shù)據(jù)時(shí),算法能適應(yīng)的做出反應(yīng)或進(jìn)行處理。
4.時(shí)間效率高和存儲(chǔ)量低:效率是指算法執(zhí)行時(shí)間,存儲(chǔ)量需求是指算法執(zhí)行過(guò)程中所需最大存儲(chǔ)空間。這是最常用的用來(lái)考量一個(gè)好的算法的標(biāo)準(zhǔn)。也就是時(shí)間復(fù)雜度與空間復(fù)雜度。
2.2算法時(shí)間復(fù)雜度
在進(jìn)行算法分析時(shí),語(yǔ)句的總執(zhí)行次數(shù)T(n)是關(guān)于問(wèn)題規(guī)模n的函數(shù). 進(jìn)而分析T(n)隨著n變化情況并確定T(n)的數(shù)量級(jí). 算法的時(shí)間復(fù)雜度,也就是算法的時(shí)間量度. T(n) = O(f(n)).
“它表示隨問(wèn)題規(guī)模n的增大,算法執(zhí)行時(shí)間的增長(zhǎng)率和f(n)的增長(zhǎng)率相同,稱作算法的漸近時(shí)間復(fù)雜度,簡(jiǎn)稱為時(shí)間復(fù)雜度。其中f(n)是問(wèn)題規(guī)模n的某個(gè)函數(shù)?!?br>
大寫(xiě)O( )來(lái)體現(xiàn)算法時(shí)間復(fù)雜度的記法,我們稱之為大O記法。
算法的時(shí)間復(fù)雜度是衡量一個(gè)算法好壞的重要指標(biāo)。一般情況下,隨著規(guī)模n的增大,次數(shù)T(n)的增長(zhǎng)較慢的算法為最優(yōu)算法。常見(jiàn)時(shí)間復(fù)雜度有常數(shù)階 O(1)、 線性階 O(n)、平方階 O(n^2)、 對(duì)數(shù)階 O(logn)、立方階 O(n^3)、 nlog階 O(nlogn)
-
指數(shù)階(不考慮) O(2^n)或者O(n!) 除非是非常小的n,否則會(huì)造成噩夢(mèng)般的時(shí)間消耗. 這是一種不切實(shí)際的算法時(shí)間復(fù)雜度. 一般不考慮!從小到大依次排列:O(1) < O(log2n) < O(n) < O(n2)<O(n3) ····<O(n!)
常見(jiàn)的算法時(shí)間復(fù)雜度.jpg
舉個(gè)例子,看下面3個(gè)代碼:
1、{++x;}
2、for(i = 1; i <= n; i++) { ++x; }
3、for(j = 1; j <= n; j++)
for(j = 1; j <= n; j++)
{ ++x; }
上述含有 ++x 操作的語(yǔ)句的頻度分別為1 、n 、n^2,
假設(shè)問(wèn)題的規(guī)模擴(kuò)大了n倍,3個(gè)代碼的增長(zhǎng)率分別是1 、n 、n^2
它們的時(shí)間復(fù)雜度分別為O(1)、O(n )、O(n^2)
2.21時(shí)間復(fù)雜度的計(jì)算方法
1.用常數(shù)1取代運(yùn)行時(shí)間中所有加法常數(shù);
2.在修改后的運(yùn)行次數(shù)函數(shù)中,只保留最高階項(xiàng);
3.如果在最高階項(xiàng)存在且不是1,則去除與這個(gè)項(xiàng)相乘的常數(shù);
2.22常見(jiàn)的時(shí)間復(fù)雜度
1.常數(shù)階
//1+1+1 = 3 O(1)
void testSum1(int n){
int sum = 0; //執(zhí)行1次
sum = (1+n)*n/2; //執(zhí)行1次
printf("testSum1:%d\n",sum);//執(zhí)行1次
}
//1+1+1+1+1+1+1 = 7 O(1)
void testSum2(int n){
int sum = 0; //執(zhí)行1次
sum = (1+n)*n/2; //執(zhí)行1次
sum = (1+n)*n/2; //執(zhí)行1次
sum = (1+n)*n/2; //執(zhí)行1次
sum = (1+n)*n/2; //執(zhí)行1次
sum = (1+n)*n/2; //執(zhí)行1次
printf("testSum2:%d\n",sum);//執(zhí)行1次
}
//x=x+1; 執(zhí)行1次
void add(int x){
x = x+1;
}
這個(gè)算法的運(yùn)行次數(shù)函數(shù)是f(n) = 3;根據(jù)我們大O時(shí)間復(fù)雜度表示,第一步先把常數(shù)項(xiàng)改成1. 在保留最高階時(shí)發(fā)現(xiàn),它根本沒(méi)有最高階. 所以這個(gè)算法的視覺(jué)復(fù)雜度為O(1);
2.線性階
/*2.線性階時(shí)間復(fù)雜度*/
//x=x+1; 執(zhí)行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; //執(zhí)行1次
for (i = 1; i <= n; i++) { //執(zhí)行n+1次
sum += i; //執(zhí)行n次
}
printf("testSum3:%d\n",sum); //執(zhí)行1次
}
3.對(duì)數(shù)階
/*3.對(duì)數(shù)階*/
/*2的x次方等于n x = log2n ->O(logn)*/
void testA(int n){
int count = 1; //執(zhí)行1次
//n = 10
while (count < n) {
count = count * 2;
}
}
4.平方階
/*4.平方階*/
//x=x+1; 執(zhí)行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; //執(zhí)行1次
for (i = 1; i <= n; i++) { //執(zhí)行n+1次
for (j = 1; j <= n; j++) { //執(zhí)行n(n+1)
x++; //執(zhí)行n*n次
sum = sum + x; //執(zhí)行n*n次
}
}
printf("testSum5:%d\n",sum);
}
5.立方階
/*5.立方階*/
void testB(int n){
int sum = 1; //執(zhí)行1次
for (int i = 0; i < n; i++) { //執(zhí)行n次
for (int j = 0 ; j < n; j++) { //執(zhí)行n*n次
for (int k = 0; k < n; k++) {//執(zhí)行n*n*n次
sum = sum * 2; //執(zhí)行n*n*n次
}
}
}
}

2.3算法空間復(fù)雜度
空間復(fù)雜度(space complexity)作為算法所需存儲(chǔ)空間的量度,記做S(n) = O (f(n))。其中,n為問(wèn)題的規(guī)模;f(n)為語(yǔ)句關(guān)于n的所占存儲(chǔ)空間的函數(shù)。
一般情況下,一個(gè)程序在機(jī)器上運(yùn)行時(shí),除了需要存儲(chǔ)程序本身的指令、常數(shù)、變量和輸入數(shù)據(jù)外,還需要存儲(chǔ)對(duì)數(shù)據(jù)操作的存儲(chǔ)單位。若輸入數(shù)據(jù)所占空間只取決于問(wèn)題本身,和算法無(wú)關(guān),這樣只需要分析該算法在實(shí)現(xiàn)時(shí)所需的輔助單元即可。若算法執(zhí)行時(shí)所需的輔助空間相對(duì)于輸入數(shù)據(jù)量而言是個(gè)常量,則稱此算法為原地工作,空間復(fù)雜度為O(1)。
程序空間計(jì)算因素:
- 寄存本身的指令
- 常數(shù)
- 變量
- 輸入
- 對(duì)數(shù)據(jù)進(jìn)行操作的輔助空間
在考量算法的空間復(fù)雜度,主要考慮算法執(zhí)行時(shí)所需要的輔助空間.
空間復(fù)雜度計(jì)算:
//問(wèn)題: 數(shù)組逆序,將一維數(shù)組a中的n個(gè)數(shù)逆序存放在原數(shù)組中.
#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};
//算法實(shí)現(xiàn)(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]);
}
//算法(1),僅僅通過(guò)借助一個(gè)臨時(shí)變量temp,與問(wèn)題規(guī)模n大小無(wú)關(guān),所以其空間復(fù)雜度為O(1);
//算法實(shí)現(xiàn)(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;
}
算法(2),借助一個(gè)大小為n的輔助數(shù)組b,所以其空間復(fù)雜度為O(n).
注意,算法的空間復(fù)雜度指的并不是整個(gè)算法在內(nèi)存占用空間,而是指的是該算法在實(shí)現(xiàn)時(shí)所需要的輔助空間就可以
