資料結構簡單要點總結

眉頭一皺計上心來發表於2011-12-09
第三章 棧、佇列和陣列
 
一、棧
棧是只能在一端進行插入和刪除的線性表
(別看只是個定義,非常重要,已經道出了運算方法:只能在一端插入和刪除。)
 
棧的特徵:後進先出,先進後出。
 
插入和刪除元素的一端稱為棧頂。(說明了我們在棧頂操作)
另一端稱為棧底。
插入元素和刪除元素的操作稱為入棧出棧
1.順序棧
結構:(top總是指向陣列最後的元素,比如data[n],而不是前面)
#define MAXSIZE 100
typedef struct
{
    elementtype data[MAXSIZE];
    int top;
} seqstack;
 
初始化棧:
void init_stack(seqstack *S)
{
    S->top = -1;    //一個元素也沒有,注意因為TOP是下標而不是元素個數,用-1
}
 
判斷棧是否為空:
int stack_empty(seqstack *S)
{
    if (S->top == -1)
        return 1;
    else
        return 0;
}
 
取棧頂元素:
elementtype stack_top(seqstack *S)
{
    if (stack_empty(S))
        error("棧為空!");
    else
        return S->data[S->top];
}
 
入棧:
void push_stack(seqstack *S, elementtype x)
{
    if (S->top == MAXSIZE -1)
        error("溢位!");
    else
        S->data[++S->top] = x;    //注意->運算子的優先順序是最高的
}
 
出棧:
elementtype pop_stack(seqstack *S)
{
    if (stack_empty(S))
        error("棧為空!");
    else
        return S->data[S->top--];
}
 
判斷棧是否為滿:
int stack_full(seqstack *S)
{
    if (S->top == MAXSIZE -1)
        return 1;
    else
        return 0;
}

總體來說,順序棧很簡單,出的時候取最後的元素,進的時候一樣進在尾部。
 
2.鏈棧
棧的鏈式儲存結構稱為鏈棧。
其插入和刪除操作僅限制在表頭位置上進行
由於只能在連結串列頭部進行操作,故鏈棧沒有必要象單連結串列那樣新增頭結點。棧頂指標就是連結串列的頭指標。
結構:
typedef struct node    //和一般連結串列的結構一樣。
{
    elementtype data;
    struct node *next;
} linkstack; 
linkstack *top;
當top=NULL時,鏈棧為空棧。
 
入棧:
void push_stack(linkstack *top, elementtype x)
{
    linkstack *P = (linkstack *)malloc(sizeof(linkstack));
    P->data = x;
    P->next = top->next;
    top = P;
}
 
出棧:
elementype pop_stack(linkstack *top)
{
    elementtype x;
    linkstack *P;
    if (top == NULL)
        error("棧為空!");
    else
    {
        x = top->data;
        P = top;
        top = top->next;
        free(P);
        return x;
    }
}
二、佇列
佇列是只能在一端插入,另一端刪除的線性表。
特徵是:先進先出,後進後出。
 
1.順序佇列
注意順序佇列多是迴圈佇列,這裡要注意幾點:
(1)front是隊頭的前一個位置
(2)尾部入隊,頭部出隊
(3)由於迴圈,任何的位置移動計算之後要取餘:P = (P + 1) % MAXSIZE 
結構:
#define MAXSIZE 100
typedef struct
{
    elementtype data[MAXSIZE];
    int front;    //頭序號(注意是隊頭的前一個位置)
    int rear;    //尾序號(直接指向尾元素)
} seqqueue;
 
初始化佇列:
void init_queue(seqqueue *Q)
{
    Q->front = 0;
    Q->rear = 0;
}
還有一種寫法:
void init_queue(seqqueue *Q)
{
    Q->front = MAXSIZE - 1;
    Q->rear = MAXSIZE - 1;
}
兩種方法的區別是第一種插入第一個元素是data[1],而第二種是data[0]。
判斷佇列是否為空:
int queue_empty(seqqueue *Q)
{
    if (Q->front == Q->rear)
        return 1;
    else
        return -1;
}
 
判斷佇列是否為滿:
int queue_full(seqqueue *Q)
{
    if ((Q->rear + 1) % MAXSIZE == Q->front)
        return 1;
    else
        return 0;
}
 
取隊頭元素:
elementtype queue_front(seqqueue *Q)
{
    if (queue_empty(Q))
        error("佇列為空!");
    else
        return Q->data[(Q->front + 1) % MAXSIZE];
}
 
入隊:
void Enqueue(seqqueue *Q, elementtype x)
{
    if (queue_full(Q))
        error("佇列滿!");
    else
    {
        Q->rear = (Q->rear + 1) % MAXSIZE;    //千萬不能直接用Q->rear++,在迴圈佇列要特別注意
        Q->data[Q->rear] = x;
    }
}
 
出隊:
elementtype Outqueue(seqqueue *Q)
{
    if (queue_empty(Q))
        error("佇列為空!");
    else
    {
        Q->front = (Q->front + 1) % MAXSIZE;
        return Q->data[Q->front];
    }
}

2.鏈佇列
出隊時,刪除表頭操作,入隊時,在表尾新增結點。(也就是頭部出,尾部進
使用帶頭結點的單連結串列形式。(注意鏈棧是不帶頭結點的)
結構:
typedef struct mynode
{
    elementtype data;
    mynode *next;
} node;    //就是單連結串列
typedef struct
{
    node *front;
    node *rear;
} linkqueue;
 
初始化佇列:
void init_queue(linkqueue *Q)
{
    Q->front = (node *)malloc(sizeof(node));    //生成頭結點(注意是NODE型別,Q結構是已有的一個結構,這裡有點特殊,仔細體會)
    Q->rear = Q->front;
    Q->front = NULL;
}

判斷佇列是否為空:
int queue_empty(linkqueue *Q)
{
    if (Q->front == Q->rear)
        return 1;
    else
        return 0;
}
 
取隊頭元素:
elementtype queue_front(linkqueue *Q)
{
    if (queue_empty(Q))
        error("佇列為空!");
    else
        return Q->front->next->data;
}
入隊:
void Enqueue(linkqueue *Q, elementtype x)
{
    node *P = (node *)malloc(sizeof(node));
    P->data = x;
    P->next = NULL;
    Q->rear->next = P;
    Q->rear = P;
}
出隊:
elementtype Outqueue(linkqueue *Q)
{
    node *P;
    elmenttype x;
    if (queue_empty(Q))
        error("佇列為空!");
    else
    {
        P = Q->front->next;
        Q->front->next = P->next;
        x = P->data;
        free(P);
    }
    if (Q->front->next == NULL)    //只剩一個結點刪除後佇列為空時的特殊情況,一定要注意處理
        Q->rear = Q->front;
    return x;
}

三、陣列
主要是稀疏矩陣的壓縮儲存:
當陣列中非零元素非常少時,稱之為稀疏矩陣。
儲存特別如下:
(1)對稀疏矩陣壓縮儲存時,除了儲存非零元素的值v以外,還要儲存其行列號i和j,故每個元素對應一個三元組(i, j, v)。將這些元素的三元組組織起來構成三元組表。
(2)需要在三元組表中增設元素個數、行列數,以唯一確定一個稀疏矩陣。
結構如下:
#define MAXSIZE 100
typedef struct    //三元組結構
{
    int i, j;
    elementtype v;
} tuple;
typedef struct
{
    int mu, nu, tu;    //行數、列數、非0元素個數
    tuple data[MAXSIZE];
} spmatrix;

 
一、樹
樹中的每個結點最多隻有一個前驅(父輩),但可能有多個後繼(後代)。
一個結點的是指該結點的孩子數目。
若一個結點的度為0,稱為葉子結點或終結點,否則稱為分支結點或非終結點。
一棵樹的度是樹中最大的結點的度。
某個結點的子樹的根稱為其孩子結點,而該結點為其孩子結點的雙親結點或父結點
同一個結點的孩子互相稱為兄弟結點
根的次為1,其餘結點的層次為父結點的層次數加1,而最大的層次數稱為樹的高度或深度
如果樹中各兄弟結點之間的排列次序是無關的,則稱之為有序樹,否則稱為無序樹
稱多棵樹為森林
 
二、二叉樹
二叉樹和樹一樣,都可以為空樹
注意二叉樹每個結點的孩子都有左右之分,每個結點都有左右兩個子樹,這與樹結構明顯不同。
二叉樹和樹本質上是完全不同的兩種結構
 定義:滿二叉樹是指每層都有最大數目結點的二叉樹,即高度為k的滿二叉樹中有2k-1個結點。而完全二叉樹則是指在滿二叉樹的最下層從右到左連續地刪除若干個結點所得到的二叉樹。 

 

二叉樹的性質:

1.在二叉樹的第i層上的結點個數<=2i-1(i>0)
2.深度(高度)為k的二叉樹的結點個數<=2k-1
3.對任一棵非空的二叉樹,如果其葉子數為n0, 度為2的結點數為n2, 則有下面的關係式成立:n0=n2+1
(這個性質很重要。主要是有個概念:除去根結點,每個結點都與一個它上面的分支一一對應,也就是說,結點數=分支數+1,所以有:n-1=n1+2*n2) 
4.有n個結點的完全二叉樹(n>0)的深度為[log2n]+1([]為取整)
5.在編號的完全二叉樹中,各結點的編號之間的關係為:
編號為i的結點如果存在左孩子,則其編號為2i,如果存在右孩子,則其編號為2i+1,如果存在父結點,則其編號為[i/2]。
 
二叉樹的儲存結構:
1.順序儲存結構:
按完全二叉樹的編號次序進行,即編號為i的結點儲存在陣列中下標為i的元素中。
缺點:若二叉樹不是完全二叉樹,則為了保持結點之間的關係,不得不空出許多元素來,這就造成了空間的浪費。
 
2.二叉連結串列儲存結構:
void preorder(bitree *T)
{
    if (T != NULL)
    {
        visit(T);    //一般用的最多的就是輸出
        preorder(T->lchild);
        preorder(T->rchild);
    }
}

四、線索二叉樹
線索二叉樹主要是為了求解在某種次序下的前驅或後繼結點。
將二叉樹各結點中的空的左孩子指標域改為指向其前驅,空的右孩子指標域改為指向其後繼。稱這種新的指標(前驅或後繼)為線索,所得到的二叉樹被稱為線索二叉樹,將二叉樹轉變成線索二叉樹的過程稱為線索化。
同時,為了區分到底指標是指向前驅(後繼)還是孩子,要加入兩個標誌來判斷。
結構:
typedef struct node
{
    int ltag, rtag;    //0為孩子,1為前驅或後繼
    datatype data;
    struct node *lchild, *rchild;
} ordertree;
 
先序後繼的求解:
ordertree *presuc(ordertree *P)
{
    if (P->ltag == 0)
        return P->lchild;
    else
        return P->rchild;
}
中序後繼:
ordertree *insuc(ordertree *P)
{
    ordertree *q = P->rchild;
    if (P->rtag == 1)
        return q;
    else
    {
        while (q->ltag == 0)
            q = q->lchild;
        return q;
    }
}
中序先驅:
ordertree *infore(ordertree *P)
{
    ordertree *q = P->lchild;
    if (P->ltag == 1)
        return q;
    else
    {
        while (q->rtag == 0)
            q = q->rchild;
        return q;
    }
}
後序先驅:
ordertree *postfore(ordertree *P)
{
    if (P->rtag == 0)
        return P->rchild;
    else
        return P->lchild;
}

五、樹和森林
1.樹的儲存結構:
(1)雙親表示法
struct tnode
{
    datatype data;
    int parent;
}
struct tnode treelist[MAXSIZE];    //整個樹的儲存陣列說明
其中parent指示該結點父結點的下標,data存放結點的值。
優點:便於搜尋相應結點的父結點和祖先結點。
缺點:若要搜尋孩子結點或後代結點需要搜尋整個表,浪費時間。
 
(2)孩子連結串列表示法
分別將每個結點的孩子結點連成一個連結串列,然後將各表頭指標放在一個表中構成一個整體結構。
typedef struct node    //連結串列中每個孩子結點的定義
{
    int data;
    struct node *next;
} listnode;
typedef struct    //陣列元素的定義,每個陣列元素都是一個單連結串列,單頭元素不同
{
    datatype info;
    listnode *firstchild;
} arrnode;
arrnode tree[MAXSIZE];    //MAXSIZE為所有結點的個數


優缺點:與雙親表示法恰好相反。
 
(3)孩子-兄弟連結串列表示法二叉連結串列表示法,二叉樹表示法
樹中每個結點用一個連結串列結點來儲存,每個連結串列結點中除了存放結點的值外,還有兩個指標,一個用來指示該結點的第一個孩子,另一個用於指示該結點的下一個兄弟結點
typedef struct node
{
    datatype data;
    struct node *firstchild, *nextbrother;
} tnode;
 
2.樹(森林)與二叉樹的轉換
樹或森林的子樹轉換為二叉樹的左子樹,兄弟轉化為右子樹
 
3.樹(森林)的遍歷
樹的遍歷可分為先序遍歷後序遍歷。(注意沒有中序,因為樹有不只兩個孩子)即結點是在其子樹之前還是之後訪問。
遍歷樹(森林)要轉換為遍歷其對應的二叉樹:
先序遍歷:(同二叉樹的先序遍歷)

void preorder(tnode *T)
{
    if (T != NULL)
    {
        visit(T);
        preorder(T->firstchild);
        preorder(T->nextbrother);
    }
}
後序遍歷:(同二叉樹的中序遍歷)
void postorder(tnode *T)
{
    if (T != NULL)
    {
        postorder(T->firstchild);
        visit(T);
        postorder(T->nextbrother);
    }
}

 圖
圖中將每個物件用一個頂點表示,並常用一個序號來標識一個頂點。
其中弧表示單向關係邊表示雙向關係,用離散數學中的術語來說,則分別表示為非對稱關係和對稱關係。
弧用<A, B>表示(A為尾,B為頭),邊用(A, B)表示。
一個圖G由兩部分內容構成,即頂點(vertex)集合(V)和邊(或弧edge)的集合(E),並用二元組(V, E)來表示,記做G = (V, E) 
根據頂點間的關係是否有向而引入有向圖無向圖
給每條邊或弧加上權值,這樣的帶權圖稱為網路。
若無向圖中任意兩點間都有一條邊,則稱此圖G為無向完全圖。(共有邊數 n*(n-1)/2 )
若有向圖中任意一個頂點到其餘各點間均有一條弧,則稱為有向完全圖。(共有弧數 n*(n-1) )
若一個圖G1是從G中選取部分頂點和部分邊(或弧)組成,則稱G1是G的子圖。(注意,頂點和邊必須都為子關係
 
若無向圖中兩個頂點i, j之間存在一條邊,則稱i, j相鄰接,並互為鄰接點
在有向圖中,若存在弧<Vi, Vj>,也做Vi, Vj相鄰接,但為區別弧的頭、尾頂點,可進一步稱做Vi鄰接到Vj,Vj鄰接於Vi。
 
與一個頂點相鄰接的頂點數稱為該頂點的
在有向圖中,進入一個頂點的弧數稱為該頂點的入度,從一個頂點發出的弧數為該頂點的出度,並將入度和出度之和作為該頂點的度
 
一個頂點經過一定的可經路程到達另一個頂點,就為頂點之間的路徑
若某路徑所經過的頂點不重複,則稱此路徑為簡單路徑
若某路徑的首尾相同,則稱此路徑為迴路(或稱為環)。
若某迴路的中間不重複,則稱之為簡單迴路
 
若無向圖中任意兩點之間均存在路徑,則稱G為連通圖,否則不連通,就存在若干個連通分量
若有向圖中任意兩點間可以互相到達,則稱為強連通圖
 
一個無向圖,連通並且無迴路,稱這樣的圖為
若有向圖中僅有一個頂點的入度為0,其餘頂點的入度都為1,稱此圖為有向樹,入度為0的頂點為根。
 
圖的儲存結構:
1。鄰接矩陣表示
對n個頂點的圖來說,其鄰接矩陣為n*n階的。
鄰接矩陣的元素存放邊(弧)的權值,對不存在的邊(弧),則用0或∞表示。
定義格式如下:
#define n 6    /* 圖頂點數 */ 
#define e 8    /* 圖的邊(弧)數 */
typedef struct
{
    vextype vexs[n];    /* 頂點型別 */
    datatype arcs[n][n];    /* 權值型別 */
} graph; 
建立一個無向網路的演算法: 
CreateGraph(graph *G) 
{ 
    int i, j, k; 
    float w; 
    for (i=0; i<n; i++) 
        G->vexs[i] = getchar();    /* 讀入頂點資訊,建立表,這裡用字元型 */ 
    for (i=0; i<n; i++) 
        for (j=0; j<n; j++) 
            G->arcs[i][j] = 0;    /* 鄰接矩陣初始化 */ 
    for (k=0; k<e; k++) 
    { 
        scanf("%d%d%f", &i, &j, &w);    /* 讀入邊(vi, vj)上的權w(暫用float型別) */ 
        G->arcs[i][j] = w; 
        G->arcs[j][i] = w; 
    } 
}

2.鄰接表表示法
將每個頂點的鄰接點連成連結串列,並將各連結串列的表頭指標合在一起(用陣列或連結串列表示均可),其中每個頭指標與該結點的資訊合為一個整體結點。
如果將鄰接表中各頂點的鄰接表變為其前驅頂點即可,從而得到逆鄰接表
用鄰接表儲存網路時,需要將各條邊(弧)的權值作為相應鄰接結點中的一個欄位。
結構:
typedef struct node
{
    int adjvex;    /* 鄰接點域 */
    struct node *next;    /* 鏈域 */
    datatype arc;    /* 權值 */
} edgenode;    /* 邊表指標 */
typedef struct
{
    vextype vertex;    /* 頂點資訊 */
    edgenode *link;    /* 邊表頭指標 */
} vexnode;    /* 頂點表結點 */
vexnode gnode[n];    /* 整個圖的構成 */
 建立無向圖的鄰接表:
CreateAdjlist(gnode)
{
    int i, j, k;
    edgenode *s;
    for (i=0; i<n; i++)    /* 讀入頂點資訊 */
    {
        gnode[i].vertex = getchar();
        gnode[i].link = NULL;    /* 邊表指標初始化 */
    }
    for (k=0; k<e; k++)    /* 建立邊表 */
    {
        scanf("%d%d", &i, &j);    /* 讀入邊(vi,vj)的頂點序號 */
        s = malloc(sizeof(edgenode));    /* 生成鄰接點序號為j的表結點 */
        s->adjvex = j;
        s->next = gnode[i].link;
        gnode[i].link = s;    /* 將*s插入頂點vi的邊表頭部(插到頭部比尾部簡單) */
        s = malloc(sizeof(edgenode));    /* 生成鄰接點序號為i的邊表結點*s */
        s->adjvex = i;
        s->next = gnode[j].link;
        gnode[j].link = s;    /* 將*s插入頂點vj的邊表頭部(最後四行由於是無向圖,所以相互,兩次) */
    }
}

圖的遍歷演算法及其應用
1.深度遍歷
(1)訪問V0
(2)依次從V0 的各個未被訪問的鄰接點出發深度遍歷
(兩句話說的非常清楚。是一種以深度為絕對優先的訪問。)
 
2。深度優先搜尋遍歷演算法
由於實際演算法比較複雜,這裡演算法依賴兩個函式來求解(對於不同的儲存結構有不同的寫法)
firstadj(G, v):返回圖G中頂點v的第一個鄰接點。若不存在,返回0。
nextadj(G, v, w):返回圖G中頂點v的鄰接點中處於w之後的那個鄰接點。若不存在,返回0。
depth first search:
void dfs(graph G, int v)
{
    int w;
    visit(v);
    visited[v] = 1;
    w = firstadj(G, v)
    while (w != 0)
    {
        if (visited[w] == 0)
            dfs(w);
        w = nextadj(G, v, w);
    }
}

如果不是連通圖,或者是有向圖,那麼訪問一個v不可能遍歷所有頂點。所以,需要再選擇未被訪問的頂點作為起點再呼叫dfs.
 
所以,深度遍歷圖的演算法如下:
void dfs_travel(graph G)
{
    int i;
    for (i=1; i<=n; i++)
        visited[i] = 0;        //初始化各頂點的訪問標誌
    for (i=1; i<=n; i++)
        if (visited[i] == 0)
            dfs(G, i);
}

3.廣度優先搜尋遍歷演算法
廣度優先搜尋遍歷演算法(bfs)是一種由近而遠的層次遍歷演算法,從頂點V0出發的廣度遍歷bfs描述為:
(1)訪問V0(可作為訪問的第一層);
(2)假設最近一層的訪問頂點依次為V1, V2, ..., Vk,則依次訪問他們的未被訪問的鄰接點。
(3)重複2,直到找不到未被訪問的鄰接點為止。
void bfs(graph G, int V0)
{
    int w;
    int v;
    queue Q;
    init_queue(Q);
    visit(V0);
    visited[V0] = 1;
    Enqueue(Q, V0);
    while (!empty(Q))
    {
        v = Outqueue(Q);
        w = firstadj(G, v);
        while (w != 0)
        {
            if (visited[w] == 0)
            {
                visit(w);
                visited[w] = 1;
                Enqueue(Q, w);
            }
            w = nextadj(G, v, w);
        }
    }
}
 
廣度遍歷圖的演算法和深度一樣:
void bfs_travel(graph G)
{
    int i;
    for (i=1; i<=n; i++)
        visited[i] = 0;
    for (i=1; i<=n; i++)
        if (visited[i] = 0)
            bfs(G, i);
}

最小生成樹:
從圖中選取若干條邊,將所有頂點連線起來,並且所選取的這些邊的權值之和最小。
這樣所選取的邊構成了一棵樹,稱這樣的樹為生成樹,由於權值最小,稱為最小生成樹。
 
構造最小生成樹有兩種方法:
1.Prim演算法:
首先將所指定的起點作為已選頂點,然後反覆在滿足如下條件的邊中選擇一條最小邊,直到所有頂點成為已選頂點為止(選擇n-1條邊):一端已選,另一端未選。
(簡單的說,就是先任選一點,然後每次選擇一條最小權值的邊,而且只連線到一個已選頂點)
 
2.Kruskal演算法:
反覆在滿足如下條件的邊中選出一條最小的,和已選邊不夠成迴路。
(條件就是不夠成迴路就OK,反覆選最小邊,知道所有頂點都有連線)
 
 
最短路徑:
一般即是要一個頂點到其餘各個頂點的最短路徑。(比如隔很遠的頂點,要繞哪幾條邊走)
求解方法:
首先,我們要畫一個表,每個頂點有path和dist兩個值,分別用來儲存到各點的最短路徑(比如(1,5,6),就是1-5-6這個路徑)和相應的長度(到該點的權值之和)。
(1)對V以外的各頂點,若兩點間的鄰接路徑存在,則將其作為最短路徑和最短長度存到path[v]和dist[v]中。(實際上也就是最開始對頂點的直接後繼進行處理)
(2)從未解頂點中選擇一個dist值最小的頂點v,則當前的path[v]和dist[v]就是頂點v的最終解(從而使v成為已解頂點)。
(3)如果v的直接後繼經過v會更近一些,則修改v的直接後繼的path和dist值。
 
(上面的確是很難懂,只能通過例子自己慢慢熟悉。)
 查詢
 
在軟體設計中,通常是將待查詢的資料元素集以某種表的形式給出,從而構成一種新的資料結構--查詢表
表包括一些“元素”,“欄位”等等概念。

在一個資料表中,若某欄位的值可以標識一個資料元素,則稱之為關鍵字(或)。
若此關鍵字的每個值均可以唯一標識一個元素,則稱之為主關鍵字,否則,若該關鍵字可以標識若干個元素,則稱之為次關鍵字
 
查詢演算法的時間效能一般以查詢次數來衡量。所謂查詢長度是指查詢一個元素所進行的關鍵字的比較次數。常以平均查詢次數、最大查詢次數來衡量查詢演算法的效能。
 
一、簡單順序查詢
int seq_seach(elementtype A[], int n, keytype x)
{
    int i;
    A[0].key = x;        //設定監視哨
    for (i=n; A[i].key!=x; i--);
    return i;
}


監視哨是一個小技巧,查詢失敗時,這裡設定的資料是A[1]-A[n],肯定可以在A[0]中找到該元素,並返回0表示查詢失敗。如果不設定監視哨,則在每次迴圈中要判斷下標是否越界:for (i=1; i!=n&&A[i].key!=x;i--); 可以節省一半的時間。
 
二、有序表的二分查詢
int bin_search(elementtype A[], int n, keytype x)
{
    int mid, low, high;
    low = 0;
    high = n - 1;
    while (low <= high)
    {
        mid = (low + high) / 2;
        if (x == A[mid].key)
            return mid;
        else if (x < A[mid].key)
            high = mid - 1;
        else
            low = mid + 1;
    }
    return -1;
}


也可以使用遞迴演算法:
int bin_search(elementtype A[], int low, int high, keytype x)
{
    int mid;
    if (low < high)
        return -1;
    else
    {
        mid = (low + high) / 2;
        if (x == A[mid].key)
            return mid;
        else if (x < A[mid],key)
            return bin_search(A, low, mid - 1, x);
        else
            return bin_search(A, mid - 1, high, x);
    }
}


 排序
 
增排序和減排序:如果排序的結果是按關鍵字從小到大的次序排列的,就是增排序,否則就是減排序。
內部排序和外部排序:如果在排序過程中,資料表中所有資料均在記憶體中進行,則這類排序為內部排序,否則就是外部排序。
穩定排序和不穩定排序:在排序過程中,如果關鍵字相同的兩個元素的相對次序不變,則稱為穩定排序,否則是不穩定排序。
 
在分析演算法的時間效能時,主要以演算法中用的最多的基本操作的執行次數(或者其數量級)來衡量,這些操作主要是比較、移動和交換元素。有時,可能要用這些次數的平均數來表示。
 
一、插入排序
基本思想:把整個待排序子表看作是左右兩部分,其中左邊為有序區,右邊為無序區,整個排序過程就是把右邊無序區中的元素逐個插入到左邊的有序區中,以構成新的有序區。
實際中,開始排序時把第一個元素A[0](或A[1])看作左邊的有序區,然後把剩下的2~N個元素依次插入到有序表中。
void insert_sort(elementtype A[n+1])
{
    int i;
    for (i=2; i<=n; i++)
    {
        A[0] = A[i];        //設定監視哨,這個陣列同樣是從1開始,A[0]就設為監視哨
        j = i - 1;
        while (A[j].key > A[0].key)
        {
            A[j + 1] = A[j];
            j--;
        }
        A[j + 1] = A[0];
    }
}


明白這種方法的簡單原理:
a1 a2 a3 ... a(i-1) ai ...
先將ai臨時儲存起來,然後把a(i-1)向前只要是比ai大的向後移,再把ai填進去即可。
 
二、快速排序
速度最快的辦法!一定要掌握,考試重點。
基本思想:首先,選定一個元素作為中間元素,然後將表中所有元素與該中間元素相比較,將表中比中間元素小的元素調到表的前面,將比中間元素大的元素調到後面,再將中間數放在這兩部分之間作為分界點,這樣便得到一個劃分;然後再對左右兩部分分別進行快速排序,如此反覆,直到每個子表僅有一個元素或空表為止。
中間數一般選擇部分的第一個元素。
int partition(elementtype A[n], int s, int t)    //s,t是要排序元素的起點和終點,並返回最後中間元素位置
{
    elementtype x = A[s];    //儲存中間元素到臨時變數x,以騰出空位
    int i = s;                        //置兩端搜尋位置的初值
    int j = t;
    while (i != j)        //兩端位置重和再停止
    {
        while (i < j && A[j].key > x.key) j--;    //從後面搜尋“小”的元素
        if (i < j)        //如果找到,就調到前面的空位中
        {
            A[i] = A[j];
            i++;
        }
        while (i < j && A[i].key < x.key) i++;    //從前面搜尋“大”的元素
        if (i < j)        //如果找到,調到後面的空位中
        {
            A[j] = A[i];
            j--;
        }
    }
    A[i] = x;        //將中間數移到最終位置上
    return i;
}


整個演算法:
void quick_sort(elementtype A[n], int s, int t)    //對陣列中下標從s到t的部分進行快速排序,如果是整個表就是0, n-1
{
    int i;
    if (s < t)    //表中至少有兩個元素時
    {
        i = partition(A, s, t);    //劃分排序一次
        quick_sort(A, i + 1, t);    //對後面部分快速排序
        quick_sort(A, s, i - 1);    //對前面部分快速排序
    }
}




三、選擇排序:
在待排序子表中完整地比較一遍以確定最大(小)元素,並將該元素放在子表的最前(後)面。 
【注:可能發覺和冒泡法比較類似,但注意選擇法是全部比較一遍,找到最小元素的下標,再進行一次交換,而冒泡則是進行多次交換】
void select_sort(elementtype A[n])
{
   int min, i, j;
   elementtype temp;
   for (i=0; i<n-1; i++)
   {
      min = i;
      for (j=i+1; j<n; j++)
         if (A[min].key > A[j].key) min = j;
      if (min != i)
      {
         temp = A[i];
         A[i] = A[min];
         A[min] = temp;
      }
   }
}


 
四、歸併排序
所謂歸併是指將兩個或兩個以上的有序表合併成一個新的有序表。
歸併演算法:
假設兩個序列A[m]和B[n]為非降序列(即存在相同元素的升序列),現要把他們合併為一個非降序列C[m+n]。
void merge(elementtype A[], elementtype B[], elementtype C[], int m, int n)
{
    int ia = 0, ib = 0, ic = 0;
    while (ia < m && ib < n)
        if (A[ia] <= B[ib])
            C[ic++] = A[ia++];
        else
            C[ic++] = B[ib++];
    while (ia < m)
        C[ic++] = A[ia++];
    while (ib < n)
        C[ic++] = B[ib++];
}





相關文章