基本數據結構和算法回顧


最近想回過頭來看看以前寫的一些代碼,可嘆為何剛進大學的時候不知道要養成寫博客的好習慣。現在好多東西都沒有做記錄,后面也沒再遇到相同的問題,忘的都差不多了。只能勉強整理了下面寫的一些代碼,這些代碼有的有參考別人的代碼,但都是自己曾經一點點敲的,掛出來,雖然很基礎,但希望能對別人有幫助。

鏈表

鏈表是一種非常基本的數據結構,被廣泛的用在各種語言的集合框架中。

首先鏈表是一張表,只不過鏈表中的元素在內存中不一定相鄰,並且每個元素都可帶有指向另一個元素的指針。

鏈表有,單項鏈表,雙向鏈表,循環鏈表等。

單項鏈表的數據結構

如下

1 typedef struct NODE{
2     struct NODE * link;
3     int value;
4 } Node;

對鏈表的操作

主要有增刪查

 1 Node * create(){
 2     Node * head,* p,* tail;
 3     //  這里創建不帶頭節點的鏈表
 4     head=NULL;
 5     do
 6     {
 7         p=(Node*)malloc(LEN);
 8         scanf("%ld",&p->value);
 9         
10         if(p->value ==0) break;
11         //  第一次插入
12         if(head==NULL)
13             head=p;
14         else
15             tail->link=p;
16         tail=p;
17     }
18     while(1);
19     tail->link=NULL;
20     return head;
21 }
22 
23 int delet(Node **linkp,int del_value){
24     register Node * current;
25     Node * m_del;
26 
27     //尋找正確的刪除位置,方法是按順序訪問鏈表,直到到達等於的節點
28     while((current = *linkp)!=NULL  &&  current->value != del_value)
29     {    
30         linkp = &current->link;
31     }
32 
33     if(NULL==current)
34         return FALSE;
35     else
36     {
37         //把該節點刪除,返回TRUE
38         m_del=current->link;
39         free(current);
40         *linkp=m_del;
41     }
42     return TRUE;
43 }
44 //需要形參為鏈表頭指針的地址和要插入的值
45 int insert(Node **linkp,int new_value){
46     register Node * current;
47     Node * m_new;
48 
49     //尋找真確的插入位置,方法是按順序訪問鏈表,直到到達其值大於或等於新插入值的節點
50     while((current = *linkp)!=NULL  &&  current->value < new_value)
51     {    
52         linkp = &current->link;
53     }
54     //為新節點分配內存,並把新值存到新節點中,如果分配失敗,返回FALSE
55     m_new =(Node*)malloc(LEN);
56     if(NULL==m_new)
57         return FALSE;
58     m_new->value = new_value;
59     //把新節點放入鏈表,返回TRUE
60 
61     m_new->link = current;
62     *linkp=m_new;
63 
64     return TRUE;
65 }

僅僅只需要將尾指針指向頭節點,就可以構成單項循環鏈表,即tail->link=head;,有的時候,可能需要將鏈表逆置,當然,如果需要逆置,最好一開始就做雙向鏈表。

 1 Node * reverse(Node * head){
 2     Node * p,*q;
 3     q= head;
 4     p = head->link;
 5     head = NULL;
 6     while(p)
 7     {    
 8         //  接到新鏈表里面去
 9         q->link = head; 
10         head  = q;
11         //  繼續遍歷原來的鏈表
12         q = p;
13         p = p->link;
14     }
15     q->link = head; 
16     head  = q;
17     return  head;
18 }

刪除鏈表中所有值為x的節點,以及清除鏈表中重復的節點

 1 //    功能:    刪除鏈表中所有值為x的節點
 2 //    形參:    1、若不帶頭結點,便是鏈表頭指針的地址,即&head
 3 //            2、若帶頭結點,便是鏈表頭節點的next域的地址,即&head->next
 4 //    形參:    為鏈表頭指針的地址和要刪除的值
 5 void del_link(Node ** plink,int x){
 6     register Node * current;
 7     while((current = *plink)!=NULL)
 8     {
 9         // 處理連續出現x的情況
10         while(current && current->data == x){
11             //  保留指向下一個節點的指針
12             Node * temp = current;
13             * plink = current = current->next;
14             //  刪除當前節點
15             free(temp);
16         }
17         
18         //向下遍歷鏈表
19         if (current)
20         {
21             plink = &current->next;
22         }
23     }
24 }
25 //    功能:    刪除鏈表中重復多余的節點
26 //    形參:    1、若不帶頭結點,便是鏈表頭指針的地址,即&head
27 //            2、若帶頭結點,便是鏈表頭節點的next域的地址,即&head->next
28 void del_linkAll(Node ** plink){
29     register Node * current;
30     while((current = *plink) != NULL){
31         //注意,這里取指向下一個元素的指針的地址,這樣刪除是會保留這一個節點
32         del_link(&current->next,current->data);
33         plink = &current->next;
34     }
35 }

對於雙向鏈表,也就是在節點中再添加一個節點,讓它與另一個指針指向的方向相反。當然,當節點有了兩個節點之后,就可以構成更復雜的比如樹圖等復雜結構了,雙向鏈表可像如下定義

 1 #ifndef __LINKLISTEX_H__
 2 #define __LINKLISTEX_H__
 3 #include <string>
 4 using std::string;
 5 //================雙向鏈表的定義===============
 6 template<class T>
 7 class DulLinkList
 8 {
 9 private:
10     typedef struct DulNode{
11         struct DulNode * prior;
12         T    data;
13         struct DulNode * next;
14     }DulNode;
15     DulNode * frist;
16     void Init();
17     void Del(DulNode * delNode);
18 public:
19     DulLinkList();
20     ~DulLinkList();
21     void AddElem(const T &  data);
22     void DelElem(const T & data);
23     string ToString()const;
24 protected:
25 };
26 #endif//__LINKLISTEX_H__

對雙向鏈表的操作也無外乎增刪改

 1 #include "LinkListEx.h"
 2 #include <iostream>
 3 using namespace  std;
 4 
 5 template<class T>
 6 DulLinkList<T>::DulLinkList(){
 7     Init();
 8 }
 9 
10 template<class T>
11 void DulLinkList<T>::Init(){
12     // 初始化第一個結點
13     this->frist = new DulNode;
14     this->frist->prior = NULL;
15     this->frist->next = NULL;
16 }
17 
18 template<class T>
19 void DulLinkList<T>::AddElem(const T & data){
20     // 直接頭部插入節點
21     DulNode * newNode = new DulNode;
22     newNode->data = data;
23     newNode->next = this->frist;
24     newNode->prior = NULL;
25     this->frist->prior = newNode;
26     this->frist = newNode;
27 }
28 
29 
30 template<class T>
31 void DulLinkList<T>::DelElem(const T & data){
32     DulNode * current = this->frist->next;
33     while (current  != NULL  && current->data != data) {
34         current = current->next;
35     }
36     if (!current)
37     {
38         return;
39     }
40     Del(current);
41 }
42 
43 template<class T>
44 void DulLinkList<T>::Del(DulNode * delNode){
45     // 調整當前節點兩端的節點的指針
46     delNode->prior->next = delNode->next;
47     delNode->next->prior = delNode->prior;
48     delete delNode;
49 }
50 template<class T>
51 DulLinkList<T>::~DulLinkList(){
52     DulNode * current = this->frist;
53     while (current)
54     {
55         DulNode * old = current;
56         current = current->next;
57         delete old;
58     }
59 }
60 
61 template<class T>
62 string DulLinkList<T>::ToString()const{
63     string res;
64     DulNode * current = this->frist->next;
65     while (current)
66     {
67         res.append(1,current->data);
68         current = current->next;
69     }
70     return res;
71 }

鏈表是個很基礎的東西,后面一些復雜的算法或數據結構的本質也是一個鏈表。鏈表和順序表(也就是數組)都可以再進一步抽象成更復雜的數據結構。

比如隊列和棧,不過是在鏈表或順序表的基礎上限制單端操作而已。再比如,由鏈表和順序表還可以構成二叉樹堆,它們還可以組合使用構成鄰接表,十字鏈表,鄰接多重表等結構用來描述圖,等等。

字符串相關算法

做里快兩年web開發了,可以說字符串是用多最多的數據類型了,所以針對字符串的算法也非常的多。先從簡單的慢慢來。

首先最基本的是對字符串的求長,連接,比較,復制等

 1 // 統計字符串長度
 2 int str_len(char *str){
 3     return *str ? str_len(str+1)+1 : 0 ;
 4 }
 5 // 字符串復制
 6 void str_cpy(char *str1,char *str2){
 7     while(*str1++ = *str2++); //當str2指向'\0'時,賦值給*str1 表達式的值為0 即為假。退出循環
 8         //if(*str1 == '\0')    // 考慮到 串2的長度大於串1的長度,防止指針越界
 9             //break;
10 }
11 // 字符串比較
12 int str_cmp(char *str1,char *str2){
13     int i;// i指向字符不同時數組下標
14     for(i=0;str1[i]==str2[i] && str1[i]!='\0' && str2[i]!='\0';i++);
15     return str1[i]-str2[i];
16 }
17 // 字符串連接
18 void str_cat(char *str1,char *str2){
19     while(*str1 != '\0')
20         str1++;
21     while(*str1++ = *str2++);
22 }

字符串比較復雜一點的就是模式匹配和子序列(編輯距離)的問題。

首先是較為簡單的BF算法,這種算法原理非常簡單,比如連個串a(主串)和b(模式串),首先將a1和b1進行比較,如果相同,則將b2與a2進行比較,如果還相同,繼續拿a3與b3比,直到b串匹配完,怎匹配完成,如果出現不同的,怎回到最初的狀態,將b1與a2進行比較,將b2與a3比較,等等,如此反復直到失敗或成功。

 1 typedef struct{
 2     char * str;
 3     int length;
 4 }String;
 5 
 6 int Index_BF(String mainStr,String modeStr,int pos){
 7     int    i = pos-1;
 8     int    j = 0;
 9     while (i<mainStr.length && j<modeStr.length)
10     {
11         if (mainStr.str[i] == modeStr.str[j])
12         {
13             i++,j++;
14         }
15         else{
16             // 出現不同的,回退到模式第一個字符的狀態,將模式右移進行匹配
17             i = i - j + 2;
18             j = 0;
19         }
20     }
21     if (j==modeStr.length)
22     {
23         return i - modeStr.length;
24     }
25     else{
26         return 0;
27     }
28 }

較為復雜的算法是KMP算法,KMP算法的關鍵是避免BF算法中的回朔。並且當匹配失敗后向右滑動到一個能自左最大對齊的位置繼續匹配。

若在ai,bj的位置匹配失敗,所以已經匹配的串便是

B1 B2 ... Bj-1 == Ai-j+1 Ai-j+2 ... Ai-1;

假設滑動完后要讓Bk與Ai對齊,則應該有

B1 B2 B3 ... Bk-1 == Ai-k+1 A-k+2 ... Ai-1;

因為是向右滑動,想一想i的位置不變,B向右滑動,很顯然,k要小於j。

所以進一步可以得到k到j之間B的子串(Bj前面的k-1個字符)與Ai前的k-1個字符是相同的,即

Bj-k+1 Bj-k+2 ... Bj-1 == Ai-k+1 Ai-k+2 ... Ai-1;

所以有

B1 B2 B3 ... Bk-1  == Bj-k+1 Bj-k+2 ... Bj-1

可以看出來,這個有個特點,字符串 B1 B2 ..... Bj-1關於Bk有種對稱的感覺,不過這個不是鏡像對稱,不過我還是喜歡這么記`對稱`,這也是求next值的依據,這個next就是k,就是偏移值。

next(j) = 0 (j==1) || max{k|1<=k<j && B1 B2 B3 ... Bk-1  == Bj-k+1 Bj-k+2 ... Bj-1} || 1;

下面是完整的KMP算法

 1 void GetNext(const char * mode,int * next){
 2     //求模式mode的next值並存入數組next中
 3     int i=1,j=0;
 4     next[1] = 0;
 5     while(i < mode[0]){
 6         if(0 == j || mode[i] == mode[j]){
 7             i++;j++;
 8             next[i] = j;
 9         }
10         else
11             j = next[j];
12     }
13 }
14 int Index_KMP(const char * str,const char * mode,int pos){
15     int i=pos,j=1;
16     int * next = (int *)malloc(sizeof(int)*(mode[0]+1));
17     next[0]=mode[0];
18     GetNext(str,next);
19     while (i<=str[0] && j<= mode[0]) {
20         if (0==j || str[i] == mode[j]) {
21             i++;j++;
22         }
23         else{
24             // 滑動模式串,注意next[j]是小於j的,這才是向右滑動
25             j = next[j];
26         }
27     }
28     return j>mode[0] ?  i - mode[0] : 0;
29 }
30 
31 void main(){
32     char string[16] = "\016abcabcabaabcac";
33     char mode[10] = "\010abaabcac";
34     printf("模式串在主串中的位置:%d\n",Index_KMP(string,mode,1));
35 }

下面的問題是最長公共子序列,算法的思想是動態規划,核心是轉義方程

,也就是當兩個字符相等時取左上元素+1,不相等時取左和上中大的那個

 1 #include <stdio.h>
 2 #include <string.h>
 3 #define MAXN 128
 4 #define MAXM MAXN
 5 int a[MAXN][MAXM];
 6 int b[MAXN][MAXM];
 7 char * str1 = "ABCBDAB";
 8 char * str2 = "BDCABA";
 9 
10 int LCS(const char *s1,int m, const char *s2,int n)
11 {
12     int i, j;
13     a[0][0] = 0;
14     for( i=1; i <= m; ++i ) {
15             a[i][0] = 0;
16     }
17     memset(b,0,sizeof(b));
18     for( i=1; i <= n; ++i ) {
19         a[0][i] = 0;
20     }
21     for( i=1; i <= m; ++i ){
22         for( j=1; j <= n; ++j ){
23             if(s1[i-1]==s2[j-1]){          //如果想等,則從對角線加1得來
24                 a[i][j] = a[i-1][j-1]+1;
25                 b[i][j] = 1;
26             }
27             else if(a[i-1][j]>a[i][j-1]){    //否則判段它上面、右邊的值,將大的數給他
28                 a[i][j]= a[i-1][j];
29                 b[i][j] = 2;
30             }
31             else{
32                 a[i][j] = a[i][j-1];
33                 b[i][j] = 3;
34             }
35 
36         }
37     }
38     return a[m][n];
39 }
40 char str[MAXN];
41 int p=0;
42 void cSubQ(int i,int j){
43     if(i<1 || j<1) return;
44     if(1==b[i][j]){
45         cSubQ(i-1,j-1);
46         str[p++]=str1[i-1];
47     }
48     else if(2 == b[i][j])
49     {
50         cSubQ(i-1,j);
51     }
52     else{
53         cSubQ(i,j-1);
54     }
55 }
56 int main()
57 {
58     int m = strlen(str1), n = strlen(str2);
59     LCS(str1,m,str2,n);
60     cSubQ(m,n);
61     str[p] = '\0';
62     printf("%s\n",str);
63     return 0;
64 }

很顯然,這個算法的時間復雜度和空間復雜度為o(n*m)

二叉樹

樹這里主要以二叉樹為例,二叉樹算是一種特殊的樹,一種特殊的圖。

二叉樹具備如下特征

  • 第i層最多有2^(i-1)次方個節點
  • 深度為k的樹最多有2^i-1個節點,也就是滿二叉樹等比求和
  • n0=n2+1,即葉子節點的數量恰好是度為2的節點數加1,主要原因是節點數總比度數多1,因為根節點沒有入度,所以有 n0 + n1 + n2 -1 = n1 + 2*n2。
  • 對於滿二叉樹,如果以有序表存儲,根節點放在0的位置上,左右孩子放在1,2上,相當於從上到下,從左到右,從0開始對節點進行編號,則對於節點i,它的左孩子應該位於2*i+1上,右孩子位於2*i+2上
  • 等等,暫時只記得這些了。

用數組和鏈表都可以存儲二叉樹,但我見過的算法大都用數組存儲二叉樹,想必鏈表雖然易於理解,但相比寫起算法來未必好寫。

對二叉樹的操作

有增刪查遍歷等操作,代碼如下。

  1 typedef struct bitnode{
  2     int m_iDate;
  3     struct bitnode * m_lChild/*左孩子指針*/,* m_rChild/*右孩子指針*/;        
  4 } CBiTNode;
  5 
  6 
  7 //建立一個帶頭結點的空的二叉樹
  8 CBiTNode * Initiate(){
  9     CBiTNode * bt;
 10     bt = (CBiTNode*)malloc(sizeof CBiTNode);
 11     bt->m_iDate = 0;
 12     bt->m_lChild = NULL;
 13     bt->m_rChild = NULL;
 14     return bt;
 15 }
 16 
 17 /*
 18 //建立一個不帶頭結點的空的二叉樹
 19 CBiTNode * Initiate(){
 20     CBiTNode * bt;
 21     bt = NULL;
 22     return bt;
 23 }
 24 */
 25 
 26 //生成一棵以x為根節點數據域信息,以lbt和rbt為左右子樹的二叉樹
 27 CBiTNode * Create(int x,CBiTNode * lbt,CBiTNode * rbt){
 28     CBiTNode * p;
 29     if((p=(CBiTNode*)malloc(sizeof CBiTNode)) ==NULL)
 30         return NULL;
 31     p->m_iDate = x;
 32     p->m_lChild = lbt;
 33     p->m_rChild = rbt;
 34     return p;
 35 }
 36 
 37 //在二叉樹bt中的parent所指節點和其左子樹之間插入數據元素為x的節點
 38 bool InsertL(int x,CBiTNode * parent){
 39     CBiTNode * p;
 40 
 41     if(NULL == parent){
 42         printf("L插入有誤");
 43         return 0;
 44     }
 45 
 46     if((p=(CBiTNode*)malloc(sizeof CBiTNode)) ==NULL)
 47         return 0;
 48 
 49     p->m_iDate = x;
 50     p->m_lChild = NULL;
 51     p->m_rChild = NULL;
 52 
 53     if(NULL == parent->m_lChild)
 54         parent->m_lChild = p;
 55     else{
 56         p->m_lChild = parent->m_lChild;
 57         parent->m_lChild = p;
 58     }
 59 
 60     return 1;
 61 }
 62 
 63 //在二叉樹bt中的parent所指節點和其右子樹之間插入數據元素為x的節點
 64 bool InsertR(int x,CBiTNode * parent){
 65     CBiTNode * p;
 66 
 67     if(NULL == parent){
 68         printf("R插入有誤");
 69         return 0;
 70     }
 71 
 72     if((p=(CBiTNode*)malloc(sizeof CBiTNode)) ==NULL)
 73         return 0;
 74 
 75     p->m_iDate = x;
 76     p->m_lChild = NULL;
 77     p->m_rChild = NULL;
 78 
 79     if(NULL == parent->m_rChild)
 80         parent->m_rChild = p;
 81     else{
 82         p->m_rChild = parent->m_rChild;
 83         parent->m_rChild = p;
 84     }
 85 
 86     return 1;
 87 }
 88 
 89 //在二叉樹bt中刪除parent的左子樹
 90 bool DeleteL(CBiTNode *parent){
 91     CBiTNode * p;
 92     if(NULL == parent){
 93         printf("L刪除出錯");
 94         return 0;
 95     }
 96 
 97     p = parent->m_lChild;
 98     parent->m_lChild = NULL;
 99     free(p);//當*p為分支節點時,這樣刪除只是刪除了子樹的根節點。子樹的子孫並沒有被刪除
100 
101     return 1;
102 }
103 
104 //在二叉樹bt中刪除parent的右子樹
105 bool DeleteR(CBiTNode *parent){
106     CBiTNode * p;
107     if(NULL == parent){
108         printf("R刪除出錯");
109         return 0;
110     }
111 
112     p = parent->m_rChild;
113     parent->m_rChild = NULL;
114     free(p);//當*p為分支節點時,這樣刪除只是刪除了子樹的根節點。子樹的子孫並沒有被刪除
115 
116     return 1;
117 }
118 
119 //二叉樹的遍歷
120 //先序遍歷二叉樹
121 bool PreOrder(CBiTNode * bt){
122     if(NULL == bt)
123         return 0;
124     else{
125         printf("bt->m_iDate == %d\n",bt->m_iDate);
126         PreOrder(bt->m_lChild);
127         PreOrder(bt->m_rChild);
128         return 1;
129     }
130 }

對二叉樹可以有先序遍歷,中序遍歷,后序遍歷得到的序列中每個元素互第一個和最后一個節點外都會有一個前驅和后驅節點。如果把前驅節點和后驅節點的信息保存在節點中就構成了線索二叉樹,顯然只要免禮一遍就能得到線索二叉樹。

二叉樹比較經典的有哈夫曼編碼問題,二叉堆等問題,二叉堆放到堆排序一起講。

哈夫曼問題就是要讓頻率高的節點編碼最短,也就是要節點在哈夫曼樹中的深度最小。

 1 //    Huffman.h
 2 #ifndef __HUFFMAN_H__
 3 #define __HUFFMAN_H__
 4 
 5 typedef struct {
 6     unsigned int weight;
 7     unsigned int parent,lchild,rchild;
 8 }HTNode,* HuffmanTree;            // 動態分配數組儲存哈夫曼樹
 9 typedef char * *HuffmanCode;    // 動態分配數組儲存哈夫曼編碼表
10 
11 #endif//__HUFFMAN_H__
 1 //    HuffmanTest.cpp
 2 #include "Huffman.h"
 3 #include <string.h>
 4 #include <malloc.h>
 5 // 函數功能:在哈夫曼編碼表HT[1...range]中選擇 parent 為0且weight最小的兩個結點,將序號分別存到s1和s2里
 6 void Select(const HuffmanTree &HT,int range,int &s1,int &s2){
 7     int i;
 8     int * pFlags;
 9     pFlags = (int *)malloc((range+1)*sizeof(int));
10     int iFlags=0;
11     for(i=1;i<=range;i++){
12         if(0 == HT[i].parent){
13             pFlags[++iFlags] = i;
14         }
15     }
16     int Min=1000;
17     int pMin=pFlags[1];
18     for(i=1;i<=iFlags;i++){
19         if(HT[pFlags[i]].weight < Min){
20             pMin = i;
21             Min=HT[pFlags[i]].weight;
22         }
23     }
24     s1=pFlags[pMin];
25     Min=1000;
26     for(i=1;i<=iFlags;i++){
27         if(pFlags[i]!=s1)
28             if(HT[pFlags[i]].weight < Min){
29                 pMin = i;
30                 Min=HT[pFlags[i]].weight;
31             }
32     }
33     s2=pFlags[pMin];
34 }
35 void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int * w, int n){
36     // w存放n個字符的權值(均>0),構造哈夫曼樹HT,並求出n個字符的哈夫曼編碼HC
37     if(n <= 1) return;
38     int m = 2*n-1;
39     HT = (HuffmanTree)malloc((m+1)*sizeof(HTNode));        // 0 單元不使用
40     int i;
41     HuffmanTree p=NULL;
42     // 初始化哈夫曼編碼表
43     for(p=HT+1,i=1;i<=n;i++,p++,w++){
44         p->weight = *w,p->parent = p->lchild = p->rchild = 0;
45     }
46     for( ;i<=m;i++,p++){
47         p->weight = p->parent = p->lchild = p->rchild = 0;
48     }
49     // 建立哈夫曼樹
50     int s1,s2;
51     for(i=n+1;i<=m;i++){
52         Select(HT,i-1,s1,s2);
53         HT[s1].parent = HT[s2].parent = i;
54         HT[i].lchild = s1,HT[i].rchild = s2;
55         HT[i].weight = HT[s1].weight+HT[s2].weight;
56     }
57 
58     // 從葉子節點到根逆向求每個字符的哈夫曼編碼
59     HC = (HuffmanCode)malloc((n+1)*sizeof(char *));        // 分配n個字符編碼的頭指針向量
60     char * cd = (char*)malloc(n*sizeof(char));            // 分配求編碼的工作空間
61     int start;                                            // 編碼起始位置
62     cd[n-1] = '\0';                                        // 編碼結束符
63     for(i=1;i<=n;i++){                                    // 逐個字符求哈夫曼編碼
64         start = n-1;                                    // 將編碼起始位置和末位重合
65         for(int c=i,f=HT[i].parent;f != 0; c=f,f=HT[f].parent){
66             if(c == HT[f].lchild)
67                 cd[--start] = '0';
68             else
69                 cd[--start] = '1';
70         }
71         HC[i] = (char*)malloc((n-start)*sizeof(char));    // 為第i個字符編碼分配空間
72         strcpy(HC[i],&cd[start]);                        // 從 cd 復制字符串到 HC
73     }
74     free(cd);
75 }

哈夫曼樹的測試數據

 1 #include "Huffman.h"
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 
 5 void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int * w, int n);
 6 void main(){
 7     system("color F0");
 8     HuffmanTree HT = NULL;
 9     HuffmanCode HC = NULL;
10     char chArr[8]={'A','B','C','D','E','F','G','H'};
11     int w[8]={7,19,2,6,32,3,21,10};
12     HuffmanCoding(HT,HC,w,8);
13     int i,j;
14     printf(    "HT    weight   parent  lchild  rchild\n");
15     for(i=1;i<=15;i++){
16         printf("%02d\t%2u\t%2u\t%2u\t%2u\n",i,HT[i].weight,HT[i].parent,HT[i].lchild,HT[i].rchild);
17     }
18     printf("\n\n字符    權值    編碼\n");
19     for(i=0;i<8;i++){
20         printf("%c\t%2d\t%-8s\n",chArr[i],w[i],HC[i+1]);
21     }
22 }

圖相關算法

圖是一種比較復雜的數據結構,圖的定義就不在此復述了。

圖的一些表示方法(存儲結構)

  • 鄰接矩陣
    • 對於一個又n個節點的圖,鄰接矩陣以一個n*n的二維數組a來描述圖,對於不同的圖,比如,有向圖和無向圖,帶權圖和無權圖,a[i,j]表示的含義有所不同,但都是描述邊的。
  • 鄰接表
    • 鄰接表組合使用數組和鏈表描述圖,其中數組的每一個元素代表一個節點i,i由兩部分組成,一部分代表節點的數據,另一部分為一個指向一鏈表,這個鏈表里存放着能從節點i出發能走到的所有節點。對於有向圖和無向圖會有不同的表示。鄰接表一般要比領接矩陣更省空間,但它帶來了求入度不便等問題。
  • 十字鏈表
    • 結合使用鄰接表與逆鄰接表,這種方式只能描述有向圖。首先它也有一個數組,每個數據元素代表一個節點i,i由三部分組成,i在鄰接表的基礎上增加了一個指針,這個指針指向第一個以i為弧尾的及節點。這就很好的解決了求入度的問題。
  • 鄰接多重表
    • 鄰接多重表主要,它主要用來表描述無向圖,在鄰接表或十字鏈表中,數組元素的指針域指向的鏈表元素其實代表了邊,如果用鄰接表來存無向圖,會使得一條邊對應的兩個節點分別位於兩條鏈中,當我需要刪除一條邊時,總是需要找到另一個表示這條邊的邊節點,再刪除。所以有了鄰接多重表,鄰接多重表就是只用一個邊界點表示邊,但是將它鏈接到兩鏈表中(對,沒有錯,一個節點,同時存在於兩個鏈表中)

下面是上面四種描述的代碼表示

 1 #ifndef __GRAPH_DEFINE_H__
 2 #define __GRAPH_DEFINE_H__
 3 
 4 #define INT_MAX            9999
 5 #define INFINITY        INT_MAX            // 最大值
 6 #define MAX_VERTEX_NUM    20                // 最大頂點個數
 7 #define VEX_FORM        "%c"            // 頂點輸入格式
 8 #define INFO_FORM        "%d"            // 邊信息輸入格式
 9 typedef int InfoType;            // 弧相關信息類型
10 typedef char VextexType;        // 頂點數據類型
11 typedef int VRType;                // 頂點關系類型,對無權圖,用0或者1表示是否相鄰。對帶權圖,則是權值類型。
12 typedef enum { DG,DN,UDG,UDN} GraphKind;// 圖類型 {有向圖,有向網,無向圖,無向網}
13 
14 //////////////////////////////////////////////////////////////////////////
15 //    鄰接矩陣存儲結構: 可存儲任意類型圖
16 //////////////////////////////////////////////////////////////////////////
17 typedef struct{
18     VRType        Adj;
19     InfoType    Info;                    // 該弧相關信息
20 }ArcCell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
21 typedef struct{
22     char cVexs[MAX_VERTEX_NUM];            // 頂點向量
23     AdjMatrix arcs;                        // 鄰接矩陣
24     int iVexNum,iArcNum;                // 圖中當前頂點數和弧數
25     GraphKind kind;                        // 圖的種類標志
26 }MGraph;
27 
28 
29 //////////////////////////////////////////////////////////////////////////
30 //    鄰接表存儲結構:    可存儲任意類型的圖
31 //////////////////////////////////////////////////////////////////////////
32 typedef struct ArcNode{
33     int                iAdjvex;        // 該弧所指向的頂點位置
34     struct ArcNode    *nextarc;        // 指向下一條弧的指針
35     InfoType        Info;            // 該弧相關信息
36 }ArcNode;
37 typedef struct VNode{
38     VextexType    cData;                // 頂點信息
39     ArcNode        *firstarc;            // 指向第一條依附該頂點的弧的指針
40 }VNode,AdjList[MAX_VERTEX_NUM];
41 typedef struct {
42     AdjList        vertices;
43     int            iVexnum,iArcnum;    // 圖的當前頂點個數和弧數
44     GraphKind    Kind;                // 圖的種類標志
45 }ALGraph;
46 
47 //////////////////////////////////////////////////////////////////////////
48 //    十字鏈表存儲結構: 只存儲有向圖
49 //////////////////////////////////////////////////////////////////////////
50 typedef struct ArcBox{
51     int                iTailVex,iHeadVex;        // 該弧的尾和頭頂點的位置
52     struct ArcBox    *hLink,*tLink;            // 分別為弧頭相同和弧尾相同的鏈域
53     InfoType        Info;                    // 該弧相關信息
54 }ArcBox;
55 typedef struct VexNode{
56     VextexType        data;
57     ArcBox            *firstIn,*firstOut;        // 分別指向了該頂點的第一條入弧和出弧
58 }VexNode;
59 typedef struct OLGraph{
60     VexNode        xlist[MAX_VERTEX_NUM];        // 表頭向量
61     int            iVexNum,iArcNum;            // 有向圖當前頂點數和弧數
62 }OLGraph;
63 
64 //////////////////////////////////////////////////////////////////////////
65 //    鄰接多重表:    存儲無向圖
66 //////////////////////////////////////////////////////////////////////////
67 typedef enum {unvisited,visited}VisitIf;
68 typedef struct EBox{
69     VisitIf            mark;            // 訪問標記
70     int                iIVex,iJVex;    // 邊依附的兩個頂點的位置
71     struct EBox        *iLink,*jLink;    // 分別指向依附這兩個頂點的下一條邊
72     InfoType        Info;            // 該邊信息指針
73 }EBox;
74 typedef struct VexBox{
75     VextexType        data;
76     EBox            *firstEdge;        // 指向第一條依附該頂點的邊
77 }VexBox;
78 typedef struct {
79     VexBox            adjmulist[MAX_VERTEX_NUM];
80     int                iVexNum,iEdgeNum;    // 無向圖當前頂點數和邊數
81 }
82 #endif//__GRAPH_DEFINE_H__

圖相關操作

對圖的操作有創建,增刪查等等,其中查就是遍歷,遍歷分為深度優先搜索和廣度優先搜索。

深度優先搜索類似於樹的鍾旭遍歷,即遇到未范圍的節點,馬上訪問,修改標記數組,然后沿着這個節點繼續訪問,直到訪問完,然后在回朔,轉向未訪問的分支。直到節點被訪問完,如果是連通圖,只要訪問進行一次深度搜索,如果是非連通的,就要搜索多次。

廣度優先搜索就像金字塔從上向下的一層一層的搜索,廣度優先搜索除了需要用標記數組記錄狀態以外,還需要用隊列來將發現而未訪問的節點記錄下來。用隊列是為了保證遍歷順序。

下面是一些圖相關的操作算法

  1 #include <stdio.h>
  2 #include "GraphDefine.h"
  3 #include "Define.h"
  4 #include <malloc.h>
  5 
  6 //////////////////////////////////////////////////////////////////////////
  7 //    鄰接矩陣圖的相關操作
  8 //////////////////////////////////////////////////////////////////////////
  9 Status CreateDG(MGraph &G);        // 構造有向圖
 10 Status CreateDN(MGraph &G);        // 構造有向網
 11 Status CreateUDG(MGraph &G);    // 構造無向圖
 12 Status CreateUDN(MGraph &G);    // 構造無向網
 13 int LocateVex(const MGraph &G,const VextexType &v);    // 獲得頂點v在圖中的位置
 14 Status CreateGraph(MGraph &G,VextexType v){
 15     // 采用數組(鄰接矩陣)表示法,構造圖G
 16     int iType;
 17     scanf("%d%*c",&iType);
 18     G.kind = (GraphKind)iType;
 19     switch(G.kind) {
 20     case DG: return CreateDG(G);        // 構造有向圖
 21     case DN: return CreateDN(G);        // 構造有向網
 22     case UDG:return CreateUDG(G);        // 構造無向圖
 23     case UDN:return CreateUDN(G);        // 構造無向網
 24     default:
 25         return ERROR;
 26     }
 27 }
 28 
 29 Status CreateUDN(MGraph &G){
 30     //  采用數組(鄰接矩陣)表示法,構造網G
 31     int i,j,k;
 32     int IncInfo;
 33     scanf("%d %d %d",&G.iVexNum,&G.iArcNum,&IncInfo);
 34     for(i=0;i<G.iVexNum;++i)            // 構造頂點向量
 35         scanf("%c%*c",&G.cVexs[i]);
 36     for(i=0;i<G.iVexNum;i++){
 37         for(j=0;j<G.iVexNum;j++){        // 初始化鄰接矩陣
 38             G.arcs[i][j].Adj = INFINITY;
 39             G.arcs[i][j].Info =NULL;        
 40         }
 41     }
 42     VextexType v1,v2;
 43     int w;
 44     for(k=0;k<G.iArcNum;k++){            // 構造鄰接矩陣
 45         scanf("%c %c %d%*c",&v1,&v2,&w);
 46         i = LocateVex(G,v1),j = LocateVex(G,v2);    // 確定v1,v2在G中的位置
 47         G.arcs[i][j].Adj = w;            // 弧<v1,v2>的權值
 48         if(IncInfo) scanf(INFO_FORM,G.arcs[i][j].Info);
 49         G.arcs[j][i] = G.arcs[i][j];    // 置<v1,v2>對稱弧<v2,v1>
 50     }
 51     return OK;
 52 }
 53 
 54 //////////////////////////////////////////////////////////////////////////
 55 //    十字鏈表圖的相關操作
 56 //////////////////////////////////////////////////////////////////////////
 57 int LocateVex(const OLGraph &G,const VextexType &v);    // 獲得頂點v在圖中的位置
 58 
 59 Status CreateDG(OLGraph &G){
 60     // 采用十字鏈表存儲表示,構造有向圖 G(G.kind = DG)
 61     InfoType IncInfo;
 62     scanf("%d %d %d",G.iVexNum,G.iArcNum,&IncInfo);
 63     int i;
 64     for(i=0;i<G.iVexNum;i++){            // 構造表頭向量
 65         scanf(VEX_FORM "%*c",G.xlist[i].data);            // 輸入頂點值
 66         G.xlist[i].firstIn = G.xlist[i].firstOut = NULL;// 初始化指針
 67     }
 68     int k,j;
 69     VextexType    v1,v2;
 70     ArcBox * p;
 71     for(k=0;k<G.iArcNum;k++){            // 輸入各弧並構造十字鏈表 
 72         scanf(VEX_FORM VEX_FORM,&v1,&v2);            // 輸入一條弧的始點和終點
 73         i = LocateVex(G,v1),j = LocateVex(G,v2);    // 確定V1和 V2在G中的位置
 74         p = (ArcBox*)malloc(sizeof(ArcBox));        // 假設有足夠的空間
 75         // 對弧節點賦值
 76         p->iTailVex = v1,p->iHeadVex = v2;
 77         p->hLink = G.xlist[j].firstIn,p->tLink = G.xlist[i].firstOut;
 78         p->Info = NULL;
 79         G.xlist[j].firstIn = G.xlist[i].firstOut = p;    // 完成在入弧與出弧的鏈頭的插入
 80         if(IncInfo) scanf(INFO_FORM,&p->Info);            //若含有相關信息,則輸入
 81     }
 82     return OK;
 83 }
 84 
 85 //////////////////////////////////////////////////////////////////////////
 86 //    深度優先搜索
 87 //////////////////////////////////////////////////////////////////////////
 88 bool visited[MAX_VERTEX_NUM];
 89 Status(*VisitFunc)(int v);
 90 int FirstAdjVex(MGraph,int);
 91 int NextAdjVex(MGraph,int);
 92 
 93 void  DFSTeaverse(MGraph G,Status (*Visit)(int v)){
 94     int v;
 95     VisitFunc = Visit;
 96     for(v=0;v<G.iVexNum;v++) visited[v] = false;
 97     for(v=0;v<G.iVexNum;v++)
 98         if(!visited[v]) DFS(G,v);
 99 }
100 
101 void DFS(MGraph G,int v){
102     visited[v] = true;
103     VisitFunc(v);
104     int w;
105     for(w = FirstAdjVex(G,v); w>0 ; w = NextAdjVex(G,v))
106         if(!visited[w]) DFS(G,w);
107 }
108 //////////////////////////////////////////////////////////////////////////
109 //    廣度優先搜索
110 //////////////////////////////////////////////////////////////////////////
111 //    隊列相關函數
112 void InitQueue(int []);
113 void EnQueue(int []);
114 bool QueueEmpty(int []);
115 void DFSTeaverse(MGraph G,Status (*Visit)(int v)){
116     int v;
117     for (v=0;v<G.iVexNum;v++)
118     {
119         visited[v] = false;
120     }
121     int Q[MAX_VERTEX_NUM];
122     InitQueue(Q);
123     for(v=0;v<G.iVexNum;v++){
124         if(!visited[v]){
125             visited[v] = true;
126             Visit(v);
127             EnQueue(Q);
128             while(!QueueEmpty(Q)){
129                 int u,w;
130                 DeQueue(Q,u);
131                 for( w = FirstAdjVex(G,u); w>=0 ; w = NextAdjVex(G,u))
132                     if(!visited[w]){
133                         visited[w] = true;
134                         Visit(w);
135                         EnQueue(Q,w);
136                     }
137             }
138         }
139     }
140 }

與圖相關的還有很多算法,比如求最小生成樹的prim算法和kruskal算法

prim算法初始化一個s集合,始終挑選與s集合相連最小的邊連接的節點加到集合中,然后更新剩余節點到s的距離,直到所有的點添加進了s集合,prim算法代碼如下

 1 #include <stdio.h>
 2 #include <string.h>
 3 #define MAXN 1024
 4 #define INF 0xeFFFFFFF
 5 int e[MAXN][MAXN];
 6 int low[MAXN];
 7 bool inSet[MAXN];
 8 int n;
 9 int prim(){
10     int res=0,i,j,k;
11     inSet[0] = true;
12     memset(inSet,0,sizeof(inSet));
13     for(i=0;i<n;i++){
14         //現在所有點到S的距離就是到0的距離
15         low[i] = e[0][i];
16     }
17     for(i=1;i<n;i++){
18         int minv = INF;
19         int p = 0;
20         //找出與集合s相連的最小的邊
21         for(j=0;j<n;j++){
22             if(!inSet[j] && low[j] < minv){
23                 minv = low[j],p = j;
24             }
25         }
26         if(minv == INF) return -1;//非連通圖
27         //將頂點j加到S里面
28         inSet[p] = true;
29         //將最短路徑加到結果里
30         res += low[p];
31         //更新low數組
32         for(k=0;k<n;k++){
33             if(!inSet && low[p] > e[p][k]){
34                 low[p] = e[p][k];
35             }
36         }
37     }
38 
39     return res;
40 }
41 
42 int main(void){
43     int i;
44     scanf("%d",&n);
45     for(i=0;i<n-1;i++){
46         int x,y,w;
47         scanf("%d%d%d",&x,&y,&w);
48         e[x][y] = e[y][x] = w;
49     }
50     int res = prim();
51     printf("%d\n",res);
52     return 0;
53 }

Kruskal算法不斷選取最小的邊i,只要biani加進來不構成回路,則加入到邊的集合e中來,直到加入的邊能連接所有的頂點,則結束算法

  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <stdlib.h>
  4 #define MAXN 1024
  5 #define INF 0xeFFFFFFF
  6 
  7 //定義邊的結構
  8 typedef struct Ele{
  9     int x,y;    //邊的端點
 10     int w;      //邊的權重
 11     bool inSet;
 12 }Ele;
 13 
 14 Ele * eles[MAXN];//對於n個頂點,最多有n*(n-1)條邊
 15 
 16 int m;//m條邊
 17 
 18 int pa[MAXN];
 19 int r[MAXN];
 20 
 21 void make_set(){
 22     int i;
 23     for(i=0;i<n;i++){
 24         pa[i] = i;
 25         r[i] = 1;
 26     }
 27 }
 28 
 29 int find_set(int x){
 30     if(x != pa[x]){
 31         pa[x] = find_set(x);
 32     }
 33     return pa[x];
 34 }
 35 
 36 bool unin_set(int x,int y){
 37     if(x==y) return;
 38     int xp = find_set(x);
 39     int yp = find_set(y);
 40     if(xp == yp) return false;//構成回路,不能合並
 41     if(r[xp]>r[yp]){
 42         pa[yp] = xp;//zhi小的放在zhi大的下面
 43     }
 44     else{
 45         pa[xp] = yp;
 46         if(r[xp] == r[yp]){
 47             r[yp]++;
 48         }
 49     }
 50     return true;
 51 }
 52 
 53 
 54 
 55 void sort(){
 56     int i,j;
 57     for(i=0;i<n-2;i++){
 58         int p = i;
 59         for(j = i+1;j<n-1;j++){
 60             if(eles[p].w > eles[j].w){
 61                 p = j;
 62             }
 63         }
 64         if(p!=i){
 65             Ele * tmp = eles[i];eles[i] = eles[p];eles[p] = tmp;
 66         }
 67     }
 68 }
 69 /*
 70 int cmp(void * a,void * b){
 71     return (Ele*)a->w - (Ele*)b->w;
 72 }
 73 */
 74 int klske(){
 75     //將邊由小到大排序
 76     //qsort(eles,sizeof(eles),sizeof(eles[0]),cmp)
 77     sort();
 78     int res;
 79     for(int i=0;i<m;i++){
 80         if(unin_set(find_set(eles[i].x),find_set(eles[i].y))){
 81             printf("%d %d %d\n",else[i].x,eles[i].y,eles[i].w);
 82         }
 83     }
 84     return res;
 85 }
 86 
 87 int main(void){
 88     int i;
 89     scanf("%d",&m);
 90     //eles = (Ele*)malloc(n*sizeof(Ele));
 91     for(i=0;i<m-1;i++){
 92         int x,y,w;
 93         scanf("%d%d%d",&x,&y,&w);
 94         eles[i] = (Ele*)malloc(sizeof(Ele));
 95         eles[i]->x = x;
 96         eles[i]->y = y;
 97         eles[i]->w = w;
 98         eles[i]->inSet = false;
 99     }
100     int res = klske(k);
101     printf("%d\n",res);
102 
103     for(i=0;i<m-1;i++){
104         free(eles[i]);
105     }
106     return 0;
107 }

 

上面主要涉及的是一些數據結構,以及這些數據結構最基本的算法,下面進入算法部分

查找算法

樹表查找

線索二叉樹

線索二叉樹要求任何幾節點的左子樹比該節點的值小,右子樹的值比該節點大。二叉排序樹,主要涉及的是插入和搜索

 1 #include <stdio.h>
 2 #include <malloc.h>
 3 
 4 typedef struct bsTree{
 5     int m_iDate;
 6     struct bsTree * m_lChild/*左孩子指針*/,* m_rChild/*右孩子指針*/;
 7 } * BsTree,BsNode ;
 8 
 9 BsTree  insert(BsTree  bs,int x){
10     BsNode * p = bs;
11     BsNode * note  = p;
12     BsNode * ct = NULL;
13     while(p){
14         if(x == p->m_iDate){
15             return p;
16         }
17         else{
18             // 記錄上一個節點
19             note = p;
20             if(x < p->m_iDate) p = p->m_lChild;
21             else p = p->m_rChild;
22         }
23     }
24 
25     ct = (BsNode * )malloc(sizeof(BsNode));
26     ct->m_iDate = x;
27     ct->m_rChild = NULL;
28     ct->m_lChild = NULL;
29     if(!bs){
30         bs = ct;
31     }else if(x < note->m_iDate){
32         note->m_lChild = ct;
33     }else note->m_rChild = ct;
34     return bs;
35 }
36 
37 BsNode * search(BsTree bs , int x){
38     if(!bs || bs->m_iDate == x){
39         return bs;
40     }else if(x < bs->m_iDate){
41         return search(bs->m_lChild,x);
42     }else{
43         return search(bs->m_rChild,x);
44     }
45 }

有序表查找

二分查找

 1 int binarySearch(int arr[],int l,int r,int key){
 2     while(l<r){
 3         int mid = (l+r) >> 1;
 4         if(arr[mid] > key){
 5             r = mid -1;
 6         }else if(arr[mid] < key){
 7             l = mid + 1;
 8         }else{
 9             return mid;
10         }
11     }
12     return -l-1; // 返回可插入位置
13 }

排序算法

冒泡排序

以升序為例,冒泡排序每次掃描數組,將逆序修正,每次恰好將最大的元素過五關斬六將推到最后,再在剩余的元素重復操作

 1 void mpSort(int a[],int n){
 2     int i,j,swaped=1;
 3     for(i=0;i<n-1 && swaped;i++){
 4         swaped = 0;
 5         for(j=0;j<n-i;j++){
 6             if(a[j] > a[j+1]){
 7                 swap(a[j],a[j+1]);
 8                 swaped = 1;
 9             }
10         }
11     }
12 }

可見,冒泡排序的平均時間復雜度為O(n^2)

選擇排序

以升序為例,每次掃描數組,找到最小的元素直接挪到第一個來,再在剩余的數組中重復這樣的操作

 1 void selectSort(int a[],int n){
 2     int i,j;
 3     for(i=0;i<n-1;i++){
 4         int p =i;
 5         for(j=i;j<n;j++){
 6             if(a[p] > a[j]){
 7                 p = j;
 8             }
 9         }
10         if(p != i){
11             swap(p,i);
12         }
13     }
14 }

選擇排序的平均時間復雜度也是O(n^2)

直接插入排序

直接插入排序不斷在前面已經有序的序列中插入元素,並將元素向后挪。再重取一個元素重復這個操作

 1 #define MAXSIZE        20
 2 
 3 typedef int KeyType;
 4 typedef struct {
 5     KeyType key;
 6 }RedType;
 7 typedef struct{
 8     RedType r[MAXSIZE+1];
 9     int length;
10 }SqList;
11 //---------------------直接插入排序---------------------
12 void InsertSort(SqList & L){
13     int i,j;
14     for (i=2;i<=L.length;i++)
15     {
16         L.r[0] = L.r[i];
17         j = i -1;
18         while (L.r[0].key < L.r[j].key)
19         {
20             L.r[j+1] = L.r[j];
21             j--;
22         }
23         L.r[j+1] = L.r[0];
24     }
25 }

插入排序的平均時間復雜度也是O(n^2)

堆排序

堆排序也是一種插入排序,不過是向二叉堆中插入元素,而且以堆排序中的方式存儲二叉堆,則二叉堆必定是一棵完全二叉樹,堆排序設計的主要操作就是插入和刪除之后的堆調整,使得堆保持為大底堆或者小底堆的狀態。也就是根節點始終保持為最小或最大,每次刪除元素,就將根節點元素與最后元素交換,然后將堆的大小減1,然后進行堆調整,如此反復執行這樣的刪除操作。很顯然,大底堆最后會得到遞增序列,小底堆會得到遞減序列。

 1 /**
 2 *  堆調整
 3 *   在節點數為n的堆中從i節點開始進行堆調整
 4 */
 5 void heapAjust(int arr[] ,int i,int n){
 6     // 從i節點開始調整堆成為小底堆
 7     int tmp = arr[i],min;
 8     // 左孩子,對於節點i,它的左孩子就是2i+1,右孩子就是2i+2;
 9     for(;(min = 2*i + 1)<n;i = min){
10         if(min+1 != n && arr[min] > arr[min+1]) min ++;
11         if(arr[i] > arr[min]){
12             //  將小元素放置到堆頂
13             arr[i] = arr[min];
14         }
15         else break;
16     }
17     arr[i] = tmp;
18 }
19 void heapSort(int arr[],int n){
20     // 建堆
21     int i;
22     for(i=n>>1;i>=0;i--){
23         heapAjust(arr,i,n);
24     }
25     // 取堆頂,調整堆
26     for(i = n-1;i>0;i--){
27         //  每次取堆頂最小的元素與最后的元素交換,最后會得到遞減序列
28         int tmp = arr[0];arr[0] = arr[i],arr[i] = tmp;
29         //  刪除一個元素后需要從根元素起重新調整堆
30         heapAjust(arr,0,i);
31     }
32 }

排序的平均時間復雜度為O(NLogN)

快速排序

說到快排,就想起了大一上學期肖老師(教我C語言的老師)與我討論的問題,當時懵懂無知,后才才知道那就是快排。

快排的思想也很簡單,以升序為例,在序列中選一標桿,一般講第一個元素作為標桿,然后將序列中比標桿小的元素放到標桿左邊,將比標桿大的放到標桿右邊。然后分別在左右兩邊重復這樣的操作。

 1 void ksort(int a[],int l,int r){
 2     //  長度小於2有序
 3     if(r - l < 2) return;
 4     int start = l,end = r;
 5     while(l < r){
 6         // 向右去找到第一個比標桿大的數
 7         while(++l < end && a[l] <= a[start]);
 8         // 向左去找第一個比標桿小的數
 9         while(--r > start && a[r] >= a[start]);
10         if(l < r) swap(a[l],a[r]); // 前面找到的兩個數相對於標桿逆序 ,需交換過來 。l==r 不需要交換,
11     }
12     swap(a[start],a[r]);// 將標桿挪到正確的位置.
13     // 對標桿左右兩邊重復算法,注意,這個l已經跑到r后面去了
14     ksort(a,start,r);
15     ksort(a,l,end);
16 }

快速排序的平均時間復雜度為O(NLogN)

合並排序

 合並排序采用分治法的思想對數組進行分治,對半分開,分別對左右兩邊進行排序,然后將排序后的結果進行合並。按照這樣的思想,遞歸做是最方便的。

 1 int a[N],c[N];
 2 void mergeSort(l,r){
 3     int mid,i,j,tmp;
 4     if(r - 1 > l){
 5         mid = (l+r) >> 1;
 6         // 分別最左右兩天排序
 7         mergeSort(l,mid);
 8         mergeSort(mid,r);
 9         // 合並排序后的數組
10         tmp  = l;
11         for(i=l,j=mid;i<mid && j<r;){
12             if(a[i] > a[j]) c[tmp++] = a[j++];
13             else c[tmp++] = a[i++];
14         }
15         //  把剩余的接上
16         if(j < r) {
17             for(;j<r;j++) c[tmp++] = a[j];
18         }else{
19             for(;i<mid;i++) c[tmp++] = a[i];
20         }
21         // 將c數組覆蓋到a里
22         for(i=l;i<r;i++){
23             a[i] = c[i];
24         }
25     }
26 }

先到這里吧,寫這么多有點辛苦,白天還有事


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM