給出的一些常見的數據結構與算法的筆試面試題,特整理以下,後期遇到新的再更新。html
常見時空複雜度有node
冒泡排序算法(重點)面試
(1)算法流程算法
a.比較兩個相鄰的元素,若是第一個比第二個大,則交換兩個元素的位置;編程
b.對每一對相鄰的元素作一樣的工做,從開始的第一對一致到結尾的最後一對,通過這一步,最後的元素將是最大值;數據結構
c.針對全部的元素重複以上步驟,除了最後一個;函數
d.持續對愈來愈少的元素重複以上步驟,直到沒有元素須要交換爲止;ui
(2)算法評價(N表明元素個數)spa
評價時間複雜度O(N^2),比較穩定的排序方法,對樣本的有序性敏感指針
插入排序算法
(1)算法流程
a.從第一個元素起,該元素能夠認爲已經有序
b.從下一個元素起依次取出,讓取出的元素依次與左邊的有序數列進行比較
c.若是左邊的元素大於取出的元素,則左邊的元素右移
d.若是左邊的元素小於等於取出的元素,則將取出的元素插入到左邊元素的右邊,或者左邊再也不有元素,則將取出的元素插入到最左邊;
e.重複以上過程,直處處理完畢全部的元素爲止
(2)算法評價
平均時間複雜度O(N^2),比較穩定的排序方法,對樣本的有序性很是敏感,可是插入排序算法的賦值次數比冒泡少,所以通常狀況下略優於冒泡排序
選擇排序
(1)算法流程
a.從第一個元素起依次取出,而且假定取出的元素爲最小值,使用min記錄該元素的下標
b.使用min記錄的元素和後續的元素依次進行比較,若是後續元素中有比min記錄的元素還小的元素,則從新記錄該元素的下標到min中,也就是後續記錄變成了min記錄的最小值
c.直到min記錄的最小值和後續全部的元素比較完畢,交換min記錄的最小值和最開始假定的元素之間的位置,此時最小值被移動到了最左邊
d.重複以上過程,直處處理完畢全部元素
(2)算法評價
平均時間複雜度O(N^2),不穩定,對樣本的有序性不敏感,雖然該算法比較的次數多,可是交換的次數少,所以通常狀況下也是略優於冒泡排序
例如 30 20 30 50 40,用選擇排序會更改兩個30的先後順序,因此不穩定
快速排序算法
(1)算法流程
a.從樣本數列中選擇中間元素做爲基準值,單獨保存起來;
b.重組樣本數列,將全部小於基準值的元素放在基準值的左邊,將全部大於基準值的元素放在基準值的右邊,這個過程叫作分組
c.以遞歸的方式分別對小於基準值的分組和大於基準值的分組進行再次分組,直處處理完畢全部的元素爲止
(2)算法評價
平均時間複雜度O(NlogN),不穩定,若是每次都能作到均勻分組,則排序速度最快
選擇、冒泡、快速、插入、希爾、歸併、堆排等。
1.快排:是冒泡排序的一種改進。
優勢:快,數據移動少
缺點:穩定性不足
2.歸併:分治法排序,穩定的排序算法,通常用於對整體無序,但局部有序的數列。
優勢:效率高O(n),穩定
缺點:比較佔用內存
希爾排序
void sort(int *array,int len){ int tmp,i,j,gap; gap = len ; do { gap = gap / 3 + 1; for(i=0+gap;i<len;i++){ if(array[i]<array[i-gap]){ tmp = array[i]; for(j=i-gap;j>=0;j=j-gap) if(array[j]>tmp) array[j+gap]=array[j]; else break; array[j+gap]=tmp; } } }while(gap > 1); }
堆排序
static void heapAdjust(int * array,int start,int end); void sort(int *array,int len){ int i,j; for(i=len/2;i>=0;i--) heapAdjust(array,i,len-1); for(i=len-1;i>0;i--){ int tmp=array[i]; array[i]=array[0]; array[0]=tmp; heapAdjust(array,0,i-1); } } static void heapAdjust(int * array,int start,int end){ int i; int tmp = array[start]; for(i=2*start+1;i<=end;i=2*i+1){ if(array[i]<array[i+1]&& i<end) i++; if(tmp > array[i]) break; array[start]=array[i]; start = i; } array[start]=tmp; }
插排、冒泡、快排
//編程實現各類排序算法 #include <stdio.h> //實現冒泡排序算法 void bubble(int arr[],int len) { int i = 0,j = 0; //使用外層循環來控制比較的輪數 for(i = 1; i < len; i++) { //使用內層循環控制針對當前輪比較的元素下標 int flag = 1; for(j = 0; j < len-i; j++) { //若是第一個元素大於第二個元素,則交換 if(arr[j] > arr[j+1]) { int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; //代表有數據元素髮生了交換 flag = 0; } } //表示剛纔的一輪掃描中沒有發生交換 if(1 == flag) { // 省略剩餘的輪數 break; } } } //實現插入排序算法 void insert(int arr[],int len) { int i = 0,j = 0; //從第二個元素起,依次取出每一個元素 for(i = 1; i < len; i++) { //使用臨時變量記錄取出的當前元素值 int temp = arr[i]; //使用取出的元素與左邊的有序數列依次比較,若是左邊的元素大,則左邊元素右移; for(j = i; arr[j-1] > temp && j >= 1; j--) { arr[j] = arr[j-1]; } //直到左邊元素再也不大於取出元素時,插入取出元素 if(j != i) { arr[j] = temp; } } } //實現選擇排序算法 void choose(int arr[],int len) { int i = 0,j = 0; //從第一個元素起依次取出,使用min記錄下標 for(i = 0; i < len-1; i++) { int min = i; //使用取出的元素與後續元素依次比較,若是找到比min記錄元素還小的元素,則從新記錄下標 for(j = i+1; j < len; j++) { if(arr[j] < arr[min]) { min = j; } } //直到min記錄的元素與後續全部元素比較完畢,交換min記錄的元素和最開始取出的元素 if(min != i) { int temp = arr[i]; arr[i] = arr[min]; arr[min] = temp; } } } //實現快速排序 void quick(int arr[],int left,int right) { //1.尋找中間元素做爲基準值,單獨保存 int p = (left + right)/2; int pivot = arr[p]; //2.分別使用左邊元素和右邊元素與基準值進行比較,將小於基準值的元素放在左邊,將大於等於基準值的元素放在右邊; int i = 0,j = 0; for(i = left,j = right; i < j; ) { //若是左邊元素存在而且小於基準值時 while(arr[i] < pivot && i < p) { i++; } //若是左邊元素存在,可是大於等於基準值 if(i < p) { arr[p] = arr[i]; p = i; } //接下來處理右邊的元素 while(pivot <= arr[j] && p < j) { j--; } if(p < j) { arr[p] = arr[j]; p = j; } } //3.將基準值放在重合的位置上 arr[p] = pivot; //4.分別對左邊分組和右邊分組重複以上過程,使用遞歸處理 if(p-left > 1) { quick(arr,left,p-1); } if(right-p > 1) { quick(arr,p+1,right); } } int main(void) { int arr[9] = {20,5,30,10,15,6,25,12,28}; //使用排序算法進行排序 //bubble(arr,9); //insert(arr,9); //choose(arr,9); quick(arr,0,8); printf("排序後的結果是:"); int i = 0; for(i = 0; i < 9; i++) { printf("%d ",arr[i]); } printf("\n"); return 0; }
鏈表
//編程實現單鏈表的各類操做 #include <stdio.h> #include <stdlib.h> //定義節點的數據類型 typedef struct Node { int data;//記錄數據元素自己 struct Node* next;//記錄下一個節點的地址 }Node; //定義單鏈表的數據類型 typedef struct { Node* head;//記錄頭節點的地址 Node* tail;//記錄尾節點的地址 int cnt;//記錄元素的個數 }List; //判斷鏈表是否爲空 empty int empty(List* pl); //判斷鏈表是否爲滿 full int full(List* pl); //計算鏈表中節點個數 size int size(List* pl); //向頭節點位置插入新元素的功能 push_head void push_head(List* pl,int data); //遍歷鏈表中的全部節點元素值 travel void travel(List* pl); //編寫建立新節點的函數 create_node Node* create_node(int data); //向鏈表的末尾追加新元素 void push_tail(List* pl,int data); //向鏈表中任意的下標位置插入新元素 void insert_data(List* pl,int pos,int data); //實現獲取頭節點的元素值 get_head int get_head(List* pl); //實現獲取尾節點的元素值 get_tail int get_tail(List* pl); //實現刪除頭節點的功能 pop_head int pop_head(List* pl); //刪除尾節點的功能函數 int pop_tail(List* pl); //刪除指定下標位置的節點 int delete_data(List* pl,int pos); //逆轉鏈表中全部節點 void reverse_data(List* pl); //實現逆轉的遞歸函數 void reverse(Node* pn); //逆序打印鏈表中的全部節點元素 void reverse_travel_data(List* pl); //實現逆序打印的遞歸函數 void reverse_travel(Node* pn); //清空鏈表中全部的節點 void clear(List* pl); int main(void) { //單鏈表的建立以及初始化 List list; list.head = NULL; list.tail = NULL; list.cnt = 0; push_head(&list,11); travel(&list);// 11 push_head(&list,22); travel(&list);// 22 11 push_head(&list,33); travel(&list);// 33 22 11 printf("鏈表中節點元素的個數是:%d\n",size(&list));// 3 printf("%s\n",empty(&list)?"鏈表已經空了":"鏈表不爲空");// 鏈表不爲空 printf("%s\n",full(&list)?"鏈表已經滿了":"鏈表沒有滿"); // 鏈表沒有滿 printf("--------------------------\n"); push_tail(&list,44); travel(&list);// 33 22 11 44 push_tail(&list,55); travel(&list);// 33 22 11 44 55 printf("鏈表中節點元素的個數是:%d\n",size(&list)); // 5 printf("---------------------------\n"); insert_data(&list,-2,66); travel(&list);//33 22 11 44 55 66 insert_data(&list,0,77); travel(&list);//77 33 22 11 44 55 66 insert_data(&list,2,88); travel(&list);//77 33 88 22 11 44 55 66 insert_data(&list,8,99); travel(&list);//77 33 88 22 11 44 55 66 99 printf("鏈表中節點元素的個數是:%d\n",size(&list)); // 9 printf("--------------------------\n"); printf("頭節點的元素值是:%d\n",get_head(&list));// 77 printf("尾節點的元素值是:%d\n",get_tail(&list));// 99 printf("刪除的頭節點元素是:%d\n",pop_head(&list));// 77 travel(&list);//33 88 22 11 44 55 66 99 printf("頭節點的元素值是:%d\n",get_head(&list));// 33 printf("尾節點的元素值是:%d\n",get_tail(&list));// 99 printf("鏈表中節點元素的個數是:%d\n",size(&list));// 8 printf("--------------------------\n"); travel(&list);//33 88 22 11 44 55 66 99 printf("刪除的尾節點是:%d\n",pop_tail(&list));// 99 printf("鏈表中節點元素的個數是:%d\n",size(&list));// 7 travel(&list);//33 88 22 11 44 55 66 printf("刪除的尾節點是:%d\n",pop_tail(&list));// 66 printf("鏈表中節點元素的個數是:%d\n",size(&list));// 6 travel(&list);//33 88 22 11 44 55 printf("---------------------------\n"); travel(&list);//33 88 22 11 44 55 printf("刪除的節點元素是:%d\n",delete_data(&list,-2)); // -1 travel(&list);//33 88 22 11 44 55 printf("刪除的節點元素是:%d\n",delete_data(&list,0));// 33 travel(&list);// 88 22 11 44 55 printf("刪除的節點元素是:%d\n",delete_data(&list,2));// 11 travel(&list);// 88 22 44 55 printf("刪除的節點元素是:%d\n",delete_data(&list,3));// 55 travel(&list);// 88 22 44 printf("--------------------------\n"); reverse_data(&list); travel(&list);// 44 22 88 printf("--------------------------\n"); printf("逆序打印的結果是:"); reverse_travel_data(&list);// 88 22 44 printf("正序打印的結果是:"); travel(&list);// 44 22 88 printf("-----------------------\n"); clear(&list); travel(&list); // 啥也沒有 return 0; } //清空鏈表中全部的節點 void clear(List* pl) { while(-1 != pop_head(pl)); } //逆序打印鏈表中的全部節點元素 void reverse_travel_data(List* pl) { //1.調用遞歸函數進行打印 reverse_travel(pl->head); //2.打印換行符增長美觀 printf("\n"); } //實現逆序打印的遞歸函數 void reverse_travel(Node* pn) { if(pn != NULL) { //1.打印後續節點元素值,使用遞歸 reverse_travel(pn->next); //2.打印頭節點元素值 printf("%d ",pn->data); } } //實現逆轉的遞歸函數 void reverse(Node* pn) { // 確保鏈表中至少有兩個節點才須要逆轉 if(pn != NULL && pn->next != NULL) { //採用遞歸逆轉後續的元素 reverse(pn->next); //逆轉兩個節點的方法 pn->next->next = pn; pn->next = NULL; } } //逆轉鏈表中全部節點 void reverse_data(List* pl) { //1.調用遞歸函數進行逆轉 reverse(pl->head); //2.交換head 和 tail的指向 Node* pt = pl->head; pl->head = pl->tail; pl->tail = pt; } //刪除指定下標位置的節點 int delete_data(List* pl,int pos) { //1.判斷下標位置是否合法 if(pos < 0 || pos >= size(pl)) { printf("下標位置不合法,刪除失敗\n"); return -1;//刪除失敗 } //2.當 pos = 0時,刪除頭節點 if(0 == pos) { return pop_head(pl); } //3.當 pos = cnt-1時,刪除尾節點 if(size(pl)-1 == pos) { return pop_tail(pl); } //4.當 pos爲其餘值時,刪除中間節點 Node* pt = pl->head; int i = 0; for(i = 1; i < pos; i++) { pt = pt->next; } Node* pm = pt->next; pt->next = pm->next; int temp = pm->data; free(pm); pm = NULL; --pl->cnt; return temp; } //刪除尾節點的功能函數 int pop_tail(List* pl) { //判斷鏈表是否爲空 if(empty(pl)) { return -1;//刪除失敗 } //當鏈表中只有一個節點時 if(1 == size(pl)) { int temp = pl->head->data; free(pl->head); pl->head = pl->tail = NULL; --pl->cnt; return temp; } //當鏈表中有更多的節點時,採用概括法 //先將相對於cnt=2時多出來的next執行完畢 Node* pt = pl->head; int i = 0; for(i = 2; i < size(pl); i++) { pt = pt->next; } //接下來寫cnt = 2時 的刪除代碼 int temp = pl->tail->data; free(pl->tail); pl->tail = pt; // 爲了不記錄已經刪除節點的地址 pl->tail->next = NULL; --pl->cnt; return temp; } //實現獲取頭節點的元素值 get_head int get_head(List* pl) { return empty(pl)?-1:pl->head->data; } //實現獲取尾節點的元素值 get_tail int get_tail(List* pl) { return empty(pl)?-1:pl->tail->data; } //實現刪除頭節點的功能 pop_head int pop_head(List* pl) { if(empty(pl)) { return -1;//刪除失敗 } Node* pt = pl->head; int temp = pt->data; pl->head = pt->next; free(pt); pt = NULL; //當鏈表中只有一個節點時,tail置爲NULL if(NULL == pl->head) { pl->tail = NULL; } --pl->cnt; return temp; } //向鏈表中任意的下標位置插入新元素 void insert_data(List* pl,int pos,int data) { //1.判斷座標是否合法 if(pos < 0 || pos > size(pl)) { //printf("座標不合法,插入失敗\n"); //return;//結束當前函數 //pos = 0; //默認插入到鏈表的頭節點位置 pos = size(pl);//默認插入到鏈表的尾部 } //2.將新元素插入指定的位置 if(0 == pos) { push_head(pl,data); return; } if(size(pl) == pos) { push_tail(pl,data); return; } //接下來就是插入到中間位置的狀況 Node* pn = create_node(data); Node* pt = pl->head; //使用for循環將相對於pos=1多出來的next走完 int i = 0; for(i = 1; i < pos; i++) { pt = pt->next; } //接下來把pos=1時的代碼寫下來便可 pn->next = pt->next; pt->next = pn; //讓節點的個數加1 ++pl->cnt; } //向鏈表的末尾追加新元素 void push_tail(List* pl,int data) { //1.建立新節點 Node* pn = create_node(data); //2.將新節點插入到鏈表的末尾 if(empty(pl)) { pl->head = pn; } else { pl->tail->next = pn; } pl->tail = pn; //3.將節點的個數 加1 ++pl->cnt; } //編寫建立新節點的函數 create_node Node* create_node(int data) { Node* pn = (Node*)malloc(sizeof(Node)); if(NULL == pn) { printf("建立節點失敗\n"); //return; exit(-1); } pn->data = data; pn->next = NULL; return pn; } //判斷鏈表是否爲空 empty int empty(List* pl) { return NULL == pl->head; } //判斷鏈表是否爲滿 full int full(List* pl) { return 0; } //計算鏈表中節點個數 size int size(List* pl) { return pl->cnt; } //向頭節點位置插入新元素的功能 push_head void push_head(List* pl,int data) { //1.建立新節點,初始化 /* Node* pn = (Node*)malloc(sizeof(Node)); if(NULL == pn) { printf("建立節點失敗\n"); return; } pn->data = data; pn->next = NULL; */ Node* pn = create_node(data); //2.將新節點插入到頭節點的位置 if(empty(pl)) { pl->head = pl->tail = pn; } else { pn->next = pl->head; pl->head = pn; } //3.節點元素的個數 加1 ++pl->cnt; } //遍歷鏈表中的全部節點元素值 travel void travel(List* pl) { printf("鏈表中的元素有:"); Node* pt = pl->head; while(pt != NULL) { printf("%d ",pt->data); pt = pt->next; } printf("\n"); }
有序二叉樹
//編程實現有序二叉樹的各類操做 #include <stdio.h> #include <stdlib.h> //定義節點的數據類型 typedef struct Node { int data;//記錄數據元素自己 struct Node* left;//記錄左子節點的地址 struct Node* right;//記錄右子節點的地址 }Node; //定義有序二叉樹的數據類型 typedef struct { Node* root;//記錄根節點的地址 int cnt;//記錄節點的個數 }Tree; //向有序二叉樹中插入元素 void insert_data(Tree* pt,int data); //實現插入節點的遞歸函數 void insert(Node** pRoot,Node* pn); //採用中序遍歷方式來遍歷有序二叉樹 void travel_data(Tree* pt); //遍歷的遞歸函數 void travel(Node* pn); //判斷有序二叉樹是否爲空 empty int empty(Tree* pt); //判斷有序二叉樹是否爲滿 full int full(Tree* pt); //獲取根節點的元素值 get_root int get_root(Tree* pt); //獲取有序二叉樹中節點的個數 size int size(Tree* pt); //清空有序二叉樹中的全部節點 void clear_data(Tree* pt); //清空的遞歸函數 void clear(Node** ppn); //查找指向目標元素指針的地址 Node** find_data(Tree* pt,int data); //查找的遞歸函數 Node** find(Node** ppn,int data); //刪除指定的元素 int delete_data(Tree* pt,int data); //修改指定的元素 void modify_data(Tree* pt,int old_data,int new_data); int main(void) { //建立有序二叉樹,而且進行初始化 Tree tree; tree.root = NULL; tree.cnt = 0; insert_data(&tree,50); travel_data(&tree);//50 insert_data(&tree,70); travel_data(&tree);//50 70 insert_data(&tree,20); travel_data(&tree);//20 50 70 insert_data(&tree,60); travel_data(&tree);//20 50 60 70 insert_data(&tree,40); travel_data(&tree);//20 40 50 60 70 printf("--------------------------\n"); printf("%s\n",empty(&tree)?"有序二叉樹已經空了":"有序二叉樹沒有空");//有序二叉樹沒有空 printf("%s\n",full(&tree)?"有序二叉樹已經滿了":"有序二叉樹沒有滿");//有序二叉樹沒有滿 printf("有序二叉樹的根節點元素是:%d\n",get_root(&tree));// 50 printf("有序二叉樹中節點元素的個數是:%d\n",size(&tree));// 5 printf("-------------------------\n"); travel_data(&tree);//20 40 50 60 70 delete_data(&tree,50); travel_data(&tree);//20 40 60 70 delete_data(&tree,40); travel_data(&tree);//20 60 70 modify_data(&tree,20,40); travel_data(&tree);//40 60 70 printf("-------------------------\n"); clear_data(&tree); travel_data(&tree); // 啥也沒有 return 0; } //修改指定的元素 void modify_data(Tree* pt,int old_data,int new_data) { //1.刪除舊元素 int res = delete_data(pt,old_data); if(-1 == res) { printf("目標元素不存在,修改失敗\n"); return; } //2.插入新元素 insert_data(pt,new_data); } //刪除指定的元素 int delete_data(Tree* pt,int data) { //查找目標元素所在的地址信息 Node** ppn = find_data(pt,data); if(NULL == *ppn) { return -1;//刪除失敗 } //將要刪除節點的左子樹合併到右子樹中 if((*ppn)->left != NULL) { insert(&(*ppn)->right,(*ppn)->left); } //尋找臨時指針記錄要刪除的節點地址 Node* pn = *ppn; //將指向要刪除節點的指針指向它的右子樹 *ppn = (*ppn)->right; //釋放要刪除的節點 free(pn); pn = NULL; //節點的個數減 1 --pt->cnt; return 0;//刪除成功 } //查找指向目標元素指針的地址 Node** find_data(Tree* pt,int data) { //調用遞歸函數進行查找 return find(&pt->root,data); } //查找的遞歸函數 Node** find(Node** ppn,int data) { //若是有序二叉樹爲空,查找失敗 if(NULL == *ppn) { return ppn; } //若是目標元素和根元素相等,則查找成功 else if(data == (*ppn)->data) { return ppn; } //若是目標元素小於根元素,則查找左子樹 else if(data < (*ppn)->data) { return find(&(*ppn)->left,data); } //若是目標元素大於根元素,則查找右子樹 else { return find(&(*ppn)->right,data); } } //清空的遞歸函數 void clear(Node** ppn) { if(*ppn != NULL) { //1.清空左子樹 clear(&(*ppn)->left); //2.清空右子樹 clear(&(*ppn)->right); //3.釋放根節點 free(*ppn); *ppn = NULL; } } //清空有序二叉樹中的全部節點 void clear_data(Tree* pt) { //調用遞歸函數來釋放全部的節點 clear(&pt->root); //節點個數 置爲0 pt->cnt = 0; } //判斷有序二叉樹是否爲空 empty int empty(Tree* pt) { return NULL == pt->root; } //判斷有序二叉樹是否爲滿 full int full(Tree* pt) { return 0; } //獲取根節點的元素值 get_root int get_root(Tree* pt) { return empty(pt)?-1:pt->root->data; } //獲取有序二叉樹中節點的個數 size int size(Tree* pt) { return pt->cnt; } //遍歷的遞歸函數 void travel(Node* pn) { if(pn != NULL) { //1.遍歷左子樹,使用遞歸 travel(pn->left); //2.遍歷根節點 printf("%d ",pn->data); //3.遍歷右子樹,使用遞歸 travel(pn->right); } } //採用中序遍歷方式來遍歷有序二叉樹 void travel_data(Tree* pt) { printf("有序二叉樹中的元素有:"); //調用遞歸函數實現遍歷 travel(pt->root); printf("\n"); } //實現插入節點的遞歸函數 void insert(Node** pRoot,Node* pn) { // Node** pRoot = &pt->root; // pRoot = &pt->root; // *pRoot = *(&pt->root) = pt->root; //1.若是有序二叉樹爲空,則直接指向新節點 if(NULL == *pRoot) { *pRoot = pn; return; } //2.若是新節點的元素值小於根節點,則插入到左子樹,使用遞歸 else if(pn->data < (*pRoot)->data) { insert(&(*pRoot)->left,pn); } //3.若是新節點的元素值大於等於根節點,則插入到右子樹中,使用遞歸 else { insert(&(*pRoot)->right,pn); } } //向有序二叉樹中插入元素 void insert_data(Tree* pt,int data) { //1.建立新節點,而且進行初始化 Node* pn = (Node*)malloc(sizeof(Node)); if(NULL == pn) { printf("建立新節點失敗\n"); return; } pn->data = data; pn->left = NULL; pn->right = NULL; //2.將新節點插入到合適的位置上,調用遞歸 insert(&pt->root,pn); //3.節點的個數 加1 ++pt->cnt; }