一、數據結構核心名詞
- 數據
程序的操作對象,用于描述客觀事物
特點:
1、可以輸入到計算機
2、可以被計算機運行
- 數據對象
性質相同的數據元素的集合(類似于數組)
- 數據元素
組成數據的對象的基本單位
- 數據項
一個數據元素由若干個數據項組成
- 結構
數據元素之間不是獨立的,存在特定的關系,這些關系即是結構
- 數據結構
指的是數據對象中的數據元素之間的關系
代碼中的體現:
#include <stdio.h>
//聲明一個結構體類型
struct Person{ //一種數據結構
char *name; //數據項--名字
char *title; //數據項--職稱
int age; //數據項--年齡
};
int main(int argc, const char * argv[]) {
struct Person p1; //數據元素;
struct Person tArray[10]; //數據對象;
p1.age = 25; //數據項
p1.name = "小C"; //數據項
p1.title = "程序猿"; //數據項
return 0;
}
二、邏輯結構與物理結構區別
邏輯結構(數據和數據之間的邏輯關系
)
- 集合結構
所有的數據都屬于同一集合
??:同學A、B、C、D都屬于一班,他們之間沒有先后順序的,是平等的。
- 線性結構
數據和數據是 一對一 的
??:數組、線性表、字符串(由n個字符組成)、棧(先進后出)、隊列
(先進先出)
- 樹形結構
數據和數據是 一對多 的
- 圖形結構
數據和數據是 多對多 的
物理結構(數據最終存儲在內存當中
)
- 順序存儲結構
開辟一段連續的內存空間,然后依次存儲進去
- 鏈式存儲結構
不需要提前開辟一個連續的內存空間
順序存儲結構與鏈式存儲結構的區別與優缺點
區別
:
1、鏈式存儲結構的地址不一定是連續的,但順序存儲結構的內存地址一定是連續的;
2、鏈式存儲結構適用于比較頻繁地插入、刪除、更新元素,而順序存儲結構適用于頻繁的查詢時使用
優缺點
:
1、空間上:順序比鏈式節約空間
2、存儲操作上:順序支持隨機存儲,方便操作
3、插入和刪除上:鏈式的要比順序的方便(因為插入的話順序也很方便,問題是順序的插入要執行更大的空間復雜度,包括一個從表頭索引以及索引后的元素后移,而鏈式是索引后,插入就完成了)
三、算法設計要求
算法
就是解決特定問題求解的描述,在計算機中表現為指令的有限序列,并且每個指令表示一個或多個操作
算法的特性
- 輸入輸出
- 有窮性
- 確定性
- 可行性
設計要求即衡量算法的要求
- 正確性
- 可讀性
- 健壯性
- 時間效率高和儲存量低
四、算法時間復雜度
大O表示法
1、用常數1取代運行時間中所有常數
??:3 -> O(1)
2、在修改運行次數函數中,只保留最高階層
??:n3+2n2+5 -> O(n3)
3、如果在最高階存在且不等于1,則去除這個項目相乘的常數
??:2n3 -> O(n3)
下面我們拿些??看看:
4.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次
}
4.2、線性階
//n+n=2n O(n)
void add2(int x,int n){
for (int i = 0; i < n; i++) { //執行n次
x = x+1; //執行n次
}
}
//1+n+1+n+1=2n+3 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次
}
4.3、對數階
//log2 n+1 O(logn)
void testA(int n){
int count = 1; //執行1次
while (count < n) {
count = count * 2; //2的x次方等于n
//x=log2 n次
}
}
4.4、平方階
//n+n^2+n^2=2n^2+n O(n^2)
void add3(int x,int n){
for (int i = 0; i< n; i++) { //執行n次
for (int j = 0; j < n ; j++) {//執行n*n次
x=x+1; //執行n*n次
}
}
}
//n+(n-1)+(n-2)+...+1 = n(n-1)/2 = n^2/2 + n/2
//等差數列公式:sn=n(a1+an)/2
//O(n^2)
void testSum4(int n){
int sum = 0;
for(int i = 0; i < n;i++)
for (int j = i; j < n; j++) {
sum += j;
}
}
//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次
}
}
}
4.5、立方階
//1+n+n^2+n^3+n^3=2n^3+n^2+n+1
//O(n^3)
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次
}
}
}
}
常見的時間復雜度
執行次數函數 | 階 | 術語 |
---|---|---|
12 | O(1) | 常數階 |
2n+3 | O(n) | 線性階 |
3n2+2n+1 | O(n2) | 平方階 |
5log2n+20 | O(logn) | 對數階 |
2n+3nlog2 n+19 | O(nlogn) | nlogn階 |
6n3+2n2+3n+4 | O(n3) | 立方階 |
2n | O(2n) | 指數階 |
O(1) < O(log n) < O(n) < O(nlog n) < O(n2) < O(n3) < O(2n)
五、算法空間復雜度計算
主要考慮算法執行時所需要額輔助空間
計算公式:S(n)=n(f(n))
其中,n為問題的規模,f(n)為語句關于n所占存儲空間的函數
問題:數組逆序,將一維數組a中的n個數逆序存放在愿數組中。
#include <stdio.h>
int main(int argc, const char * argv[]) {
// insert code here...
int n = 5;
int a[10] = {1,2,3,4,5,6,7,8,9,10};
//算法實現(1) 空間復雜度為:O(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) 空間復雜度為:O(n)
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;
}
衡量一個算法的時候是描述最壞的情況,如果最壞情況一致,那么再比較平均值
六、線性表----關于順序存儲的實現(增刪改查)
6.1、順序存儲(邏輯相鄰,物理存儲地址相鄰)
6.1.1、順序表初始化
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存儲空間初始分配量 */
typedef int Status;/* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int ElemType;/* ElemType類型根據實際情況而定,這里假設為int */
//定義結點
typedef struct{
ElemType data;
struct Node *next;
}sqlist;
//1.初始化
Status InitList(sqlist *L){
L->data = (sqlist)malloc(sizeof(ElemType)*MAXSIZE);
if(!L->data) return ERROR;
L->length=0;
return OK;
}
6.1.2、順序表的插入
/*
初始條件:順序線性表L已存在,1≤i≤ListLength(L);
操作結果:在L中第i個位置之前插入新的數據元素e,L的長度加1
*/
Status ListInsert(Sqlist *L,int i,ElemType e){
//i值不合法判斷
if((i<1) || (i>L->length+1)) return ERROR;
//存儲空間已滿
if(L->length == MAXSIZE) return ERROR;
//插入數據不在表尾,則先移動出空余位置
if(i <= L->length){
for(int j = L->length-1; j>=i-1;j--){
//插入位置以及之后的位置后移動1位
L->data[j+1] = L->data[j];
}
}
//將新元素e 放入第i個位置上
L->data[i-1] = e;
//長度+1;
++L->length;
return OK;
}
6.1.3、順序表的取值
Status GetElem(Sqlist L,int i, ElemType *e){
//判斷i值是否合理, 若不合理,返回ERROR
if(i<1 || i > L.length) return ERROR;
//data[i-1]單元存儲第i個數據元素.
*e = L.data[i-1];
return OK;
}
6.1.4、順序表刪除
/*
初始條件:順序線性表L已存在,1≤i≤ListLength(L)
操作結果: 刪除L的第i個數據元素,L的長度減1
*/
Status ListDelete(Sqlist *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;
}
6.1.5、清空順序表
/* 初始條件:順序線性表L已存在。操作結果:將L重置為空表 */
Status ClearList(Sqlist *L)
{
L->length=0;
return OK;
}
6.1.6、判斷順序表清空
/* 初始條件:順序線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE */
Status ListEmpty(Sqlist L)
{
if(L.length==0)
return TRUE;
else
return FALSE;
}
6.1.7、獲取順序表長度ListEmpty元素個數
int ListLength(Sqlist L)
{
return L.length;
}
6.1.8、順序輸出List
/* 初始條件:順序線性表L已存在 */
/* 操作結果:依次對L的每個數據元素輸出 */
Status TraverseList(Sqlist L)
{
int i;
for(i=0;i<L.length;i++)
printf("%d\n",L.data[i]);
printf("\n");
return OK;
}
6.1.9、順序表查找元素并返回位置
/* 初始條件:順序線性表L已存在 */
/* 操作結果:返回L中第1個與e滿足關系的數據元素的位序。 */
/* 若這樣的數據元素不存在,則返回值為0 */
int LocateElem(Sqlist L,ElemType e)
{
int i;
if (L.length==0) return 0;
for(i=0;i<L.length;i++)
{
if (L.data[i]==e)
break;
}
if(i>=L.length) return 0;
return i+1;
}
6.2、鏈式存儲
6.2.1、初始化單鏈表線性表
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存儲空間初始分配量 */
typedef int Status;/* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int ElemType;/* ElemType類型根據實際情況而定,這里假設為int */
//定義結點
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node * LinkList;
Status InitList(LinkList *L){
//產生頭結點,并使用L指向此頭結點
*L = (LinkList)malloc(sizeof(Node));
//存儲空間分配失敗
if(*L == NULL) return ERROR;
//將頭結點的指針域置空
(*L)->next = NULL;
return OK;
}
6.2.2、單鏈表插入
/*
初始條件:順序線性表L已存在,1≤i≤ListLength(L);
操作結果:在L中第i個位置之后插入新的數據元素e,L的長度加1;
*/
Status ListInsert(LinkList *L,int i,ElemType e){
int j;
LinkList p,s;
p = *L;
j = 1;
//尋找第i-1個結點
while (p && j<i) {
p = p->next;
++j;
}
//第i個元素不存在
if(!p || j>i) return ERROR;
//生成新結點s
s = (LinkList)malloc(sizeof(Node));
//將e賦值給s的數值域
s->data = e;
//將p的后繼結點賦值給s的后繼
s->next = p->next;
//將s賦值給p的后繼
p->next = s;
return OK;
}
6.2.3、單鏈表取值
/*
初始條件: 順序線性表L已存在,1≤i≤ListLength(L);
操作結果:用e返回L中第i個數據元素的值
*/
Status GetElem(LinkList L,int i,ElemType *e){
//j: 計數.
int j;
//聲明結點p;
LinkList p;
//將結點p 指向鏈表L的第一個結點;
p = L->next;
//j計算=1;
j = 1;
//p不為空,且計算j不等于i,則循環繼續
while (p && j<i) {
//p指向下一個結點
p = p->next;
++j;
}
//如果p為空或者j>i,則返回error
if(!p || j > i) return ERROR;
//e = p所指的結點的data
*e = p->data;
return OK;
}
6.2.4、單鏈表刪除元素
/*
初始條件:順序線性表L已存在,1≤i≤ListLength(L)
操作結果:刪除L的第i個數據元素,并用e返回其值,L的長度減1
*/
Status ListDelete(LinkList *L,int i,ElemType *e){
int j;
LinkList p,q;
p = (*L)->next;
j = 1;
//查找第i-1個結點,p指向該結點
while (p->next && j<(i-1)) {
p = p->next;
++j;
}
//當i>n 或者 i<1 時,刪除位置不合理
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;
}
6.2.5、單鏈表前插入法
/* 隨機產生n個元素值,建立帶表頭結點的單鏈線性表L(前插法)*/
void CreateListHead(LinkList *L, int n){
LinkList p;
//建立1個帶頭結點的單鏈表
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
//循環前插入隨機數據
for(int i = 0; i < n;i++)
{
//生成新結點
p = (LinkList)malloc(sizeof(Node));
//i賦值給新結點的data
p->data = i;
//p->next = 頭結點的L->next
p->next = (*L)->next;
//將結點P插入到頭結點之后;
(*L)->next = p;
}
}
6.2.6、單鏈表后插入法
/* 隨機產生n個元素值,建立帶表頭結點的單鏈線性表L(后插法)*/
void CreateListTail(LinkList *L, int n){
LinkList p,r;
//建立1個帶頭結點的單鏈表
*L = (LinkList)malloc(sizeof(Node));
//r指向尾部的結點
r = *L;
for (int i=0; i<n; i++) {
//生成新結點
p = (Node *)malloc(sizeof(Node));
p->data = i;
//將表尾終端結點的指針指向新結點
r->next = p;
//將當前的新結點定義為表尾終端結點
r = p;
}
//將尾指針的next = null
r->next = NULL;
}