數(shù)據(jù)結(jié)構(gòu) - 基本數(shù)據(jù)單位
1.數(shù)據(jù)結(jié)構(gòu)概念:
通過圖解釋:數(shù)據(jù):程序的操作對(duì)象,用于描述客觀事物。(如 二進(jìn)制數(shù)據(jù)、整型數(shù)據(jù)等)
- 數(shù)據(jù)的特點(diǎn):1.可以輸入到計(jì)算機(jī)。2.可以被計(jì)算機(jī)處理。
數(shù)據(jù)對(duì)象: 性質(zhì)相同的數(shù)據(jù)元素的集合(類似于數(shù)組)
數(shù)據(jù)元素: 組成數(shù)據(jù)的對(duì)象的基本單位
數(shù)據(jù)項(xiàng): 一個(gè)數(shù)據(jù)元素由若干數(shù)據(jù)項(xiàng)組成
結(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)系
2.數(shù)據(jù)結(jié)構(gòu)代碼舉例:
- 聲明一個(gè)結(jié)構(gòu)體:
struct Teacher{
char * name;
char * title;
int age;
}
解釋:
-
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) -- 年齡
2.使用結(jié)構(gòu)體:
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 = "GC"; //數(shù)據(jù)項(xiàng)
t1.title = "搬磚工"; //數(shù)據(jù)項(xiàng)
return 0;
}
解釋:
-
struct Teacher t1;
數(shù)據(jù)元素 -
struct Teacher tArray[10];
數(shù)據(jù)對(duì)象 -
t1.age = 18;
數(shù)據(jù)項(xiàng)
t1.name = "GC";
數(shù)據(jù)項(xiàng)
數(shù)據(jù)結(jié)構(gòu)
1.邏輯結(jié)構(gòu)
根據(jù)元素之間的關(guān)系可以分為: 線性結(jié)構(gòu) 和 非線性結(jié)構(gòu)
-
線性結(jié)構(gòu)
特點(diǎn):- 存在唯一的一個(gè)被稱作"第一個(gè)"的數(shù)據(jù)元素
- 存在唯一的一個(gè)唄稱作"最后一個(gè)"的數(shù)據(jù)元素
- 除了第一個(gè)之外,結(jié)構(gòu)中的每個(gè)數(shù)據(jù)元素均有一個(gè)前驅(qū)
- 除了最后一個(gè)之外,結(jié)構(gòu)中的每個(gè)數(shù)據(jù)元素都有一個(gè)后繼.
- 線性結(jié)構(gòu):(隊(duì)列(先進(jìn)先出 FIFO)、棧(先進(jìn)后出)、字符串、數(shù)組等)
-
非線性結(jié)構(gòu)
- 集合結(jié)構(gòu):所有數(shù)據(jù)屬于同一個(gè)集合
- 樹形結(jié)構(gòu):存在的關(guān)系是 -> 一對(duì)多 (二叉樹、紅黑樹、平衡二叉樹等)
- 圖形結(jié)構(gòu):存在的關(guān)系是 -> 多對(duì)多
邏輯結(jié)構(gòu)最終也是存在內(nèi)存中,內(nèi)存中的存儲(chǔ)結(jié)構(gòu)是物理結(jié)構(gòu)。
2.物理結(jié)構(gòu)
- 順序存儲(chǔ)結(jié)構(gòu):
- 鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu):不需要提前開辟一段內(nèi)存空間
數(shù)據(jù)結(jié)構(gòu)與算法
1.算法特征
? 有窮行: 算法可以在某一個(gè)條件下自動(dòng)結(jié)束而不是出現(xiàn)無限循環(huán)
? 確定性: 算法執(zhí)行的每一步驟在一定條件下只有一條執(zhí)行路徑,一個(gè)相同的輸入對(duì)應(yīng)相同的一個(gè)輸出
? 可行性: 算法每一步驟都必須可行,能夠通過有限的執(zhí)行次數(shù)完成
? 輸入: 算法具有零個(gè)或多個(gè)輸入
? 輸出: 算法至少有一個(gè)或多個(gè)輸出
2.算法設(shè)計(jì)要求
? 正確性
? 可讀性
? 健壯性
? 時(shí)間效率高和存儲(chǔ)量低
算法 時(shí)間復(fù)雜度
1.時(shí)間復(fù)雜度:使用大O表示法
- 用常數(shù)1取代運(yùn)行時(shí)間中所有常數(shù) 3->1 O(1)
- 在修改運(yùn)行次數(shù)函數(shù)中,只保留最高階項(xiàng) n3+2n2+5 -> O(n^3)
- 如果在最高階存在且不等于1,則去除這個(gè)項(xiàng)目相乘的常數(shù) 2n^3 -> n^3
2.時(shí)間復(fù)雜度術(shù)語(yǔ):
- 常數(shù)階
- 線性階
- 平方階
- 對(duì)數(shù)階
- 立方階
- nlog階
- 指數(shù)階(不考慮) O(2^n)或者O(n!) 除非是非常小的n,否則會(huì)造成噩夢(mèng)般的時(shí)間消耗. 這是一種不切實(shí)際的算法時(shí)間復(fù)雜度. 一般不考慮!
- 常數(shù)階
// 執(zhí)行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次
}
- 線性階
//x=x+1; 執(zhí)行n次 O(n)
void add2(int x,int n){
for (int i = 0; i < n; i++) {
x = x+1;
}
}
- 平方階
//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;
}
}
}
- 對(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;
}
}
- 立方階
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次
}
}
}
}
- nlog階(后續(xù)補(bǔ)充)
復(fù)雜度排序:
O(1) < O(log n) < O(n) < O(nlog n) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
空間復(fù)雜度
1.程序空間計(jì)算因素:
?寄存本身的指令
? 常數(shù)
? 變量
? 輸入
? 對(duì)數(shù)據(jù)進(jìn)行操作的輔助空間
注意:在考量算法的空間復(fù)雜度,主要考慮算法執(zhí)行時(shí)所需要的輔助空間
2.空間復(fù)雜度計(jì)算
通過一個(gè)問題引出 空間復(fù)雜度的計(jì)算
問題:數(shù)組逆序,將一維數(shù)組a中的n個(gè)數(shù)逆序存放在原數(shù)組中.
- 算法1 :使用中間變量
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]);
}
- 算法2
//算法實(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]);
}
線性表
ADT List{
Data: 線性表的數(shù)據(jù)對(duì)象集合為{a1,a2,......an},每個(gè)元素的類型均為DataType. 其中,除了第一個(gè)元素a1外,每一個(gè)元素有且只有一個(gè)直接前驅(qū)元素,除了最后一個(gè)元素an外,每個(gè)元素有且只有一個(gè)直接后繼元素. 數(shù)據(jù)元素之間的關(guān)系是一對(duì)一的關(guān)系.
Operation
InitList(&L)
操作結(jié)果:初始化操作,建立一個(gè)空的線性表L.
DestroyList(&L)
初始條件: 線性表L已存在
操作結(jié)果: 銷毀線性表L.
ClearList(&L)
初始條件: 線性表L已存在
操作結(jié)果: 將L重置為空表.
ListEmpty(L)
初始條件: 線性表L已存在
操作結(jié)果: 若L為空表,則返回true,否則返回false.
ListLength(L)
初始條件: 線性表L已存在
操作結(jié)果: 返回L中數(shù)據(jù)元素的個(gè)數(shù)
GetElem(L,i,&e)
初始條件: 線性表L已存在,且1<=i<ListLength(L)
操作結(jié)果: 用e返回L中第i個(gè)數(shù)據(jù)元素的值;
LocateElem(L,e)
初始條件: 線性表L已存在
操作結(jié)果: 返回L中第1個(gè)值與e相同的元素在L中的位置. 若數(shù)據(jù)不存在則返回0;
PriorElem(L,cur_e,&pre_e);
初始條件: 線性表L已存在
操作結(jié)果: 若cur_e是L的數(shù)據(jù)元素,且不是第一個(gè),則用pre_e返回其前驅(qū),否則操作失敗.
NextElem(L,cur_e,&next_e);
初始條件: 線性表L已存在
操作結(jié)果: 若cur_e是L的數(shù)據(jù)元素,且不是最后一個(gè),則用next_e返回其后繼,否則操作失敗.
ListInsert(L,i,e);
初始條件: 線性表L已存在,且1<=i<=listLength(L)
操作結(jié)果: 在L中第i個(gè)位置之前插入新的數(shù)據(jù)元素e,L長(zhǎng)度加1.
ListDelete(L,i);
初始條件: 線性表L已存在,且1<=i<=listLength(L)
操作結(jié)果: 刪除L的第i個(gè)元素,L的長(zhǎng)度減1.
TraverseList(L);
初始條件: 線性表L已存在
操作結(jié)果: 對(duì)線性表L進(jìn)行遍歷,在遍歷的過程中對(duì)L的每個(gè)結(jié)點(diǎn)訪問1次.
}ADT List.
線性表 -> 順序存儲(chǔ)
特點(diǎn):邏輯相鄰,物理存儲(chǔ)地址也相鄰
代碼實(shí)現(xiàn):
#define MAXSIZE 100 // size的最大值
#define OK 1
#define ERROR 0 // 錯(cuò)誤
#define TRUE 1
#define FALSE 0
/* ElemType類型根據(jù)實(shí)際情況而定,這里假設(shè)為int */
typedef int ElemType;
/* Status是函數(shù)的類型,其值是函數(shù)結(jié)果狀態(tài)代碼,如OK等 */
typedef int Status; // 狀態(tài)
//順序表結(jié)構(gòu)設(shè)計(jì)
typedef struct {
ElemType *data;// 數(shù)據(jù)
int length;// 數(shù)組的長(zhǎng)度
}Sqlist;
- 1.順序表的初始化
// 修改鏈表本身,需要傳入指針
Status InitList(sqlist *L){
//為順序表分配一個(gè)大小為MAXSIZE 的數(shù)組空間
L->data = malloc(sizeof(ElemType) * MAXSIZE);
//存儲(chǔ)分配失敗退出
if(!L->data) exit(ERROR);
//空表長(zhǎng)度為0
L->length = 0;
return OK;
}
main函數(shù)中調(diào)用:
int main(int argc, const char * argv[]) {
Sqlist L;
Sqlist Lb;
ElemType e;
Status iStatus;
//1.1 順序表初始化
iStatus = InitList(&L);
printf("初始化L后: L.Length = %d\n", L.length);
}
- 2.數(shù)據(jù)插入
注意事項(xiàng):1.判斷線性表是否存在 2.插入的位置是否合法 3.修改length
//1.2 順序表的插入
/*
初始條件:順序線性表L已存在,1≤i≤ListLength(L);
操作結(jié)果:在L中第i個(gè)位置之前插入新的數(shù)據(jù)元素e,L的長(zhǎng)度加1
*/
Status ListInsert(Sqlist *L,int i,ElemType e){
//i值不合法判斷
if((i<1) || (i>L->length+1)) return ERROR;
//存儲(chǔ)空間已滿
if(L->length == MAXSIZE) return ERROR;
//插入數(shù)據(jù)不在表尾,則先移動(dòng)出空余位置
if(i <= L->length){
for(int j = L->length-1; j>=i-1;j--){
//插入位置以及之后的位置后移動(dòng)1位
L->data[j+1] = L->data[j];
}
}
//將新元素e 放入第i個(gè)位置上
L->data[i-1] = e;
//長(zhǎng)度+1;
++L->length;
return OK;
}
- 3.順序表的刪除
順序表刪除
/*
初始條件:順序線性表L已存在,1≤i≤ListLength(L)
操作結(jié)果: 刪除L的第i個(gè)數(shù)據(jù)元素,L的長(zhǎng)度減1
*/
Status ListDelete(Sqlist *L,int i){
//線性表為空
if(L->length == 0) return ERROR;
//i值不合法判斷
if((i<1) || (i>L->length)) return ERROR;
for(int j = i; j < L->length;j++){
//被刪除元素之后的元素向前移動(dòng)
L->data[j-1] = L->data[j];
}
//表長(zhǎng)度-1;
L->length --;
return OK;
}
- 4.清空順序表(鏈表)
/* 初始條件:順序線性表L已存在。操作結(jié)果:將L重置為空表 */
Status ClearList(Sqlist *L)
{
L->length=0;
return OK;
}
線性表 -> 單鏈表結(jié)點(diǎn)
單鏈表結(jié)點(diǎn)結(jié)構(gòu):是由數(shù)據(jù)域和指針域組成注意:1.便于首元結(jié)點(diǎn)的處理 2.便于空表和非空表的統(tǒng)一處理
單鏈表的頭結(jié)點(diǎn):
頭結(jié)點(diǎn)的作用:
1.便于首元結(jié)點(diǎn)的處理
2.便于空表和非空表的統(tǒng)一處理
線性表 -> 鏈?zhǔn)酱鎯?chǔ)
-
單鏈表的插入
單鏈表的插入方式分為
前插法
和后插法
。-
在單鏈表的兩個(gè)數(shù)據(jù)a、b中間插入一個(gè)結(jié)點(diǎn)c,插入一個(gè)元素。(流程如下)
1、找到插入結(jié)點(diǎn)之前的結(jié)點(diǎn)指針P。
2、創(chuàng)建一個(gè)新的結(jié)點(diǎn)c。
3、給新的結(jié)點(diǎn)c數(shù)據(jù)域賦值。
4、把c的指針域next指向要插入的結(jié)點(diǎn)之后的結(jié)點(diǎn),就是b。
5、把a(bǔ)的指針指向c
單鏈表的插入.png
-
單鏈表的刪除(a,b,c)刪除b
1.找到刪除結(jié)點(diǎn)之前的結(jié)點(diǎn)的指針p
2.創(chuàng)建一個(gè)指針s指向要?jiǎng)h除的結(jié)點(diǎn)b
3.把a(bǔ)的指針域的next指向b的next
4.把s結(jié)點(diǎn)釋放free
單鏈表的刪除.png 代碼實(shí)現(xiàn)
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存儲(chǔ)空間初始分配量 */
typedef int Status;/* Status是函數(shù)的類型,其值是函數(shù)結(jié)果狀態(tài)代碼,如OK等 */
typedef int ElemType;/* ElemType類型根據(jù)實(shí)際情況而定,這里假設(shè)為int */
//定義結(jié)點(diǎn)
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node * LinkList;
1.初始化單鏈表線性表
// 初始化單鏈表線性表
Status InitList(LinkList *L){
//產(chǎn)生頭結(jié)點(diǎn),并使用L指向此頭結(jié)點(diǎn)
*L = (LinkList)malloc(sizeof(Node));
//存儲(chǔ)空間分配失敗
if(*L == NULL) return ERROR;
//將頭結(jié)點(diǎn)的指針域置空
(*L)->next = NULL;
return OK;
}
2.單鏈表的插入
/*
初始條件:順序線性表L已存在,1≤i≤ListLength(L);
操作結(jié)果:在L中第i個(gè)位置之后插入新的數(shù)據(jù)元素e,L的長(zhǎng)度加1;
*/
Status ListInsert(LinkList *L,int i,ElemType e){
int j;
LinkList p,s;
p = *L;
j = 1;
//尋找第i-1個(gè)結(jié)點(diǎn)
while (p && j<i) {
p = p->next;
++j;
}
//第i個(gè)元素不存在
if(!p || j>i) return ERROR;
//生成新結(jié)點(diǎn)s
s = (LinkList)malloc(sizeof(Node));
//將e賦值給s的數(shù)值域
s->data = e;
//將p的后繼結(jié)點(diǎn)賦值給s的后繼
s->next = p->next;
//將s賦值給p的后繼
p->next = s;
return OK;
}
3.單鏈表刪除元素
/*
初始條件:順序線性表L已存在,1≤i≤ListLength(L)
操作結(jié)果:刪除L的第i個(gè)數(shù)據(jù)元素,并用e返回其值,L的長(zhǎng)度減1
*/
Status ListDelete(LinkList *L,int i,ElemType *e){
int j;
LinkList p,q;
p = (*L)->next;
j = 1;
//查找第i-1個(gè)結(jié)點(diǎn),p指向該結(jié)點(diǎn)
while (p->next && j<(i-1)) {
p = p->next;
++j;
}
//當(dāng)i>n 或者 i<1 時(shí),刪除位置不合理
if (!(p->next) || (j>i-1)) return ERROR;
//q指向要?jiǎng)h除的結(jié)點(diǎn)
q = p->next;
//將q的后繼賦值給p的后繼
p->next = q->next;
//將q結(jié)點(diǎn)中的數(shù)據(jù)給e
*e = q->data;
//讓系統(tǒng)回收此結(jié)點(diǎn),釋放內(nèi)存;
free(q);
return OK;
}
4.單鏈表的前插入
/* 隨機(jī)產(chǎn)生n個(gè)元素值,建立帶表頭結(jié)點(diǎn)的單鏈線性表L(前插法)*/
void CreateListHead(LinkList *L, int n){
LinkList p;
//建立1個(gè)帶頭結(jié)點(diǎn)的單鏈表
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
//循環(huán)前插入隨機(jī)數(shù)據(jù)
for(int i = 0; i < n;i++)
{
//生成新結(jié)點(diǎn)
p = (LinkList)malloc(sizeof(Node));
//i賦值給新結(jié)點(diǎn)的data
p->data = i;
//p->next = 頭結(jié)點(diǎn)的L->next
p->next = (*L)->next;
//將結(jié)點(diǎn)P插入到頭結(jié)點(diǎn)之后;
(*L)->next = p;
}
}
5.單鏈表的后插入
/* 隨機(jī)產(chǎn)生n個(gè)元素值,建立帶表頭結(jié)點(diǎn)的單鏈線性表L(后插法)*/
void CreateListTail(LinkList *L, int n){
LinkList p,r;
//建立1個(gè)帶頭結(jié)點(diǎn)的單鏈表
*L = (LinkList)malloc(sizeof(Node));
//r指向尾部的結(jié)點(diǎn)
r = *L;
for (int i=0; i<n; i++) {
//生成新結(jié)點(diǎn)
p = (Node *)malloc(sizeof(Node));
p->data = i;
//將表尾終端結(jié)點(diǎn)的指針指向新結(jié)點(diǎn)
r->next = p;
//將當(dāng)前的新結(jié)點(diǎn)定義為表尾終端結(jié)點(diǎn)
r = p;
}
//將尾指針的next = null
r->next = NULL;
}
main方法中的函數(shù)調(diào)用
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
Status iStatus;
LinkList L1,L;
struct Node *L2;
ElemType e;
// 單鏈表初始化
iStatus = InitList(&L);
printf("L 是否初始化成功?(0:失敗,1:成功) %d\n",iStatus);
// 單鏈表插入數(shù)據(jù)
for(int j = 1;j<=10;j++)
{
iStatus = ListInsert(&L, 1, j);
}
// 刪除第5個(gè)元素
iStatus = ListDelete(&L, 5, &e);
printf("刪除第5個(gè)元素值為:%d\n",e);
ListTraverse(L);
// 前插法整理創(chuàng)建鏈表L
iStatus = ClearList(&L);
CreateListHead(&L, 20);
printf("整理創(chuàng)建L的元素(前插法):\n");
ListTraverse(L);
// 后插法整理創(chuàng)建鏈表L
iStatus = ClearList(&L);
CreateListTail(&L, 20);
printf("整理創(chuàng)建L的元素(后插法):\n");
ListTraverse(L);
不管是前插法還是后插法:都是先處理要插入的結(jié)點(diǎn)(要插入的的結(jié)點(diǎn)需要一個(gè)指針域next去指向它,防止丟失),再更新新結(jié)點(diǎn)的指針域next的指向,最后更新首元結(jié)點(diǎn)的指向或者表尾終端結(jié)點(diǎn)的指向。