1. 程式人生 > >資料結構(排序)

資料結構(排序)

插入排序

實驗簡介

學習了前面一章的查詢,我們知道二分查詢需要先對資料進行排序,那麼這章開始我們就來講解一下幾種經典的排序演算法。

一、直接插入排序

首先我們來講直接插入排序,它的做法是:每次從無序表中取出第一個元素,把它插入到有序表的合適位置,使有序表仍然有序。第一趟比較前兩個數,然後把第二個數按大小插入到有序表中; 第二趟把第三個資料與前兩個數從前向後掃描,把第三個數按大小插入到有序表中;依次進行下去,進行了(n-1)趟掃描以後就完成了整個排序過程,如下圖所示。

直接插入排序的程式碼實現:

#include <stdio.h>
#include
<stdlib.h>
int n; /* * 直接插入排序 */ void InsertSort(int *array) { int i, j; for (i = 2; i <= n; i++) { if (array[i] < array[i - 1]) { array[0] = array[i]; array[i] = array[i - 1]; for (j = i - 2; array[0] < array[j]; j--) { array
[j + 1] = array[j]; } array[j + 1] = array[0]; } } } int main() { int i; int *array; printf("請輸入陣列的大小:"); scanf("%d", &n); array = (int*) malloc(sizeof(int) * (n + 1)); printf("請輸入資料(用空格分隔):"); for (i = 1; i <= n; i++) { scanf
("%d", &array[i]); } InsertSort(array); printf("排序後為:"); for (i = 1; i <= n; i++) { printf("%d ", array[i]); } printf("\n"); }

二、希爾排序

希爾排序也是插入排序的一種,但它在效率上要比上面的直接插入排序高,它是對直接插入排序的改進,它的基本思想是先取一個小於n的整數d1作為第一個增量,把檔案的全部記錄分組。所有距離為d1的倍數的記錄放在同一個組中。先在各組內進行直接插入排序;然後,取第二個增量d2< d1重複上述的分組和排序,直至所取的增量dt=1(dt<…< d2< d1),即所有記錄放在同一組中進行直接插入排序為止,增量序列尤為關鍵,一般的初次取序列的一半為增量,以後每次減半,直到增量為1,大致過程如下圖所示。

希爾排序的程式碼實現:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 希爾排序
 */
void ShellSort(int *array)
{
    int k = n / 2; //增量序列(僅作舉例)
    while (k > 0)
    {
        int i, j;
        for (i = k + 1; i <=n; i++)
        {
            if (array[i] < array[i - k])
            {
                array[0] = array[i];
                for (j = i - k; j > 0 && array[0] < array[j]; j -= k)
                {
                    array[j + k] = array[j];
                }
                array[j + k] = array[0];
            }
        }
        k = k / 2;
    }
}

int main()
{
    int i;
    int *array;
    printf("請輸入陣列的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * (n + 1));
    printf("請輸入資料(用空格分隔):");
    for (i = 1; i <= n; i++)
    {
        scanf("%d", &array[i]);
    }
    ShellSort(array);
    printf("排序後為:");
    for (i = 1; i <= n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

三、小結

這一章講了插入排序中的兩個經典演算法,直接插入排序和希爾排序。直接插入排序的主要思想是每次從無序表中取出第一個元素,把它插入到有序表的合適位置,使有序表仍然有序,它屬於穩定的排序,最壞時間複雜性為O(n^2),空間複雜度為O(1)。希爾排序的主要思想是分組插入,它是不穩定的排序,它的時間複雜度跟增量序列有關,在取增量序列時要保證其中的值沒有除1之外的公因子,並且最後一個增量值必為1



交換排序

介紹兩種經典的交換排序——氣泡排序和快速排序。

一、氣泡排序

氣泡排序是一種交換排序,它的主要過程是:

  1. 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
  2. 對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最後一對。比較一趟之後,最後的元素應該會是最大的數。
  3. 針對所有的元素重複以上的步驟,除了最後一個。
  4. 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。

氣泡排序的程式碼實現:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 氣泡排序
 */
void BubbleSort(int *array)
{
    int i, j, temp;
    for (i = 0; i < n - 1; i++)
    {
        for (j = 0; j < n - 1 - i; j++)
        {
            if (array[j] > array[j + 1])
            {
                temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}

int main()
{
    int i;
    int *array;
    printf("請輸入陣列的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * n);
    printf("請輸入資料(用空格分隔):");
    for (i = 0; i < n; i++)
    {
        scanf("%d", &array[i]);
    }
    BubbleSort(array);
    printf("排序後為:");
    for (i = 0; i < n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

二、快速排序

快速排序是對氣泡排序的改進,它的基本思想是通過一趟排序將資料分成兩部分,一部分中的資料都比另一部分中的資料小,再對這兩部分中的資料再排序,直到整個序列有序,如下圖所示。

快速排序的程式碼實現:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 分割使樞軸記錄的左邊元素比右邊元素小
 */
int Partition(int *array, int low, int high)
{
    int pivotkey = array[low];
    array[0] = array[low];
    while (low < high)
    {
        while (low < high && array[high] >= pivotkey)
        {
            high--;
        }
        array[low] = array[high];
        while (low < high && array[low] <= pivotkey)
        {
            low++;
        }
        array[high] = array[low];
    }
    array[low] = array[0];
    return low;
}

/*
 * 快速排序遞迴實現
 */
void QuickSort(int *array, int low, int high)
{
    if (low < high)
    {
        int pivotloc = Partition(array, low, high);
        QuickSort(array, low, pivotloc - 1);
        QuickSort(array, pivotloc + 1, high);
    }
}

int main()
{
    int i;
    int *array;
    printf("請輸入陣列的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * (n + 1));
    printf("請輸入資料(用空格分隔):");
    for (i = 1; i <= n; i++)
    {
        scanf("%d", &array[i]);
    }
    QuickSort(array, 1, n);
    printf("排序後為:");
    for (i = 1; i <= n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

三、小結

這一章講了交換排序的兩個經典演算法,氣泡排序和快速排序。氣泡排序就像水中的氣泡一樣,小的資料往上浮,它是穩定的排序,它的時間複雜度是O(n^2)。快速排序是對氣泡排序的改進,它的主要思想是通過一趟排序將資料分成兩部分,一部分中的資料都比另一部分中的資料小,再對這兩部分中的資料再排序,直到整個序列有序,它是不穩定的排序,它的時間複雜度是O(nlogn)


選擇排序

實驗簡介

選擇排序中的兩個經典演算法:簡單選擇排序和堆排序。簡單排序的思想是通過n-1次資料元素的比較,從n-i+1個記錄中選擇最小的資料,並與第i個數據進行交換,它的時間複雜度是O(n^2)。堆排序就是利用堆的特徵來進行排序,它的時間複雜度是O(nlogn)。

一、簡單選擇排序

這一章我們來講解選擇排序,首先我們來講解其中最簡單的簡單選擇排序。

簡單選擇排序的基本思想是通過n-1次資料元素的比較,從n-i+1個記錄中選擇最小的資料,並與第i個數據進行交換,如下圖所示。

簡單選擇排序的程式碼實現:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 選擇排序
 */
void SelectSort(int *array)
{
    int i, j, k, temp;
    for (i = 0; i < n; i++)
    {
        k = i;
        for (j = i + 1; j < n; j++)
        {
            if (array[j] < array[k])
            {
                k = j;
            }
        }
        if (k != i)
        {
            temp = array[i];
            array[i] = array[k];
            array[k] = temp;
        }
    }
}

int main()
{
    int i;
    int *array;
    printf("請輸入陣列的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * n);
    printf("請輸入資料(用空格分隔):");
    for (i = 0; i < n; i++)
    {
        scanf("%d", &array[i]);
    }
    SelectSort(array);
    printf("排序後為:");
    for (i = 0; i < n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

二、堆排序

通過前面二叉樹的學習,我們知道堆是完全二叉樹,有最大堆和最小堆,其中最大堆是父結點的值比子結點大,相應的最小堆就是父結點的值比子節點小。

堆排序就是利用了最大堆(或最小堆)堆頂記錄的關鍵字最大(或最小)這一特徵,使得在當前無序區中選取最大(或最小)關鍵字變得簡單。以最大堆為例,它的基本思想就是:

  1. 先將初始檔案R[1..n]建成一個最大堆,此堆為初始的無序區;
  2. 再將關鍵字最大的記錄R[1](即堆頂)和無序區的最後一個記錄R[n]交換,由此得到新的無序區R[1..n-1]和有序區R[n],且滿足R[1..n-1].keys≤R[n].key;
  3. 由於交換後新的根R[1]可能違反堆性質,故應將當前無序區R[1..n-1]調整為堆。然後再次將R[1..n-1]中關鍵字最大的記錄R[1]和該區間的最後一個記錄R[n-1]交換,由此得到新的無序區R[1..n-2]和有序區R[n-1..n],且仍滿足關係R[1..n-2].keys≤R[n1..n].keys,同樣要將R[1..n-2]調整為堆; 重複此操作直到全部有序。

下面是示例圖:

堆排序的程式碼實現:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 生成堆
 */
void HeapAdjust(int *array, int s, int m)
{
    int i;
    array[0] = array[s];
    for (i = s * 2; i <= m; i *= 2)
    {
        if (i < m && array[i] < array[i + 1])
        {
            i++;
        }
        if (!(array[0] < array[i]))
        {
            break;
        }
        array[s] = array[i];
        s = i;
    }
    array[s] = array[0];
}

/*
 * 堆排序
 */
void HeapSort(int *array)
{
    int i;
    for (i = n / 2; i > 0; i--)
    {
        HeapAdjust(array, i, n);
    }
    for (i = n; i > 1; i--)
    {
        array[0] = array[1];
        array[1] = array[i];
        array[i] = array[0];
        HeapAdjust(array, 1, i - 1);
    }
}

int main()
{
    int i;
    int *array;
    printf("請輸入陣列的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * (n + 1));
    printf("請輸入資料(用空格分隔):");
    for (i = 1; i <= n; i++)
    {
        scanf("%d", &array[i]);
    }
    HeapSort(array);
    printf("排序後為:");
    for (i = 1; i <= n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

三、小結

這一章講解了選擇排序中的兩個經典演算法,簡單選擇排序和堆排序,這兩種都是不穩定的演算法。簡單排序的思想是通過n-1次資料元素的比較,從n-i+1個記錄中選擇最小的資料,並與第i個數據進行交換,它的時間複雜度是O(n^2)。堆排序就是利用堆的特徵來進行排序,它的時間複雜度是O(nlogn),相比於快速排序來說,它最大的優點就是在最壞情況下的時間複雜度也為O(nlogn)。


歸併排序和基數排序

實驗簡介

這章講解兩個經典排序演算法,歸併排序和基數排序。歸併排序是建立在歸併操作上的一種有效的排序演算法,時間複雜度是O(nlogn)。基數排序不需要進行資料元素間的比較,時間複雜度為O(kn)。

一、歸併排序

歸併排序是建立在歸併操作上的一種有效的排序演算法,它過程為:比較a[i]和a[j]的大小,若a[i]≤a[j],則將第一個有序表中的元素a[i]複製到r[k]中,並令i和k分別加上1;否則將第二個有序表中的元素a[j]複製到r[k]中,並令j和k分別加上1,如此迴圈下去,直到其中一個有序表取完,然後再將另一個有序表中剩餘的元素複製到r中從下標k到下標t的單元,如下圖所示。

歸併排序的程式碼實現:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 合併
 */
void Merge(int *source, int *target, int i, int m, int n)
{
    int j, k;
    for (j = m + 1, k = i; i <= m && j <= n; k++)
    {
        if (source[i] <= source[j])
        {
            target[k] = source[i++];
        }
        else
        {
            target[k] = source[j++];
        }
    }
    while (i <= m)
    {
        target[k++] = source[i++];
    }
    while (j <= n)
    {
        target[k++] = source[j++];
    }
}

/* 
 * 歸併排序
 */
 void MergeSort(int *source, int *target, int s, int t)
 {
     int m, *temp;
     if (s == t)
     {
         target[s] = source[s];
     }
     else
     {
         temp = (int*) malloc(sizeof(int) * (t - s + 1));
         m = (s + t) / 2;
         MergeSort(source, temp, s, m);
         MergeSort(source, temp, m + 1, t);
         Merge(temp, target, s, m, t);
     }
 }

 int main()
 {
     int i;
    int *array;
    printf("請輸入陣列的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * n);
    printf("請輸入資料(用空格分隔):");
    for (i = 0; i < n; i++)
    {
        scanf("%d", &array[i]);
    }
    MergeSort(array, array, 0, n - 1);
    printf("排序後為:");
    for (i = 0; i < n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
 }

二、基數排序

基數排序是跟前面的幾種排序演算法完全不一樣的排序演算法,前面的排序演算法主要通過關鍵字之間的比較和移動來實現,而基數排序不需要進行關鍵字之間的比較,它是藉助多關鍵字的思想來實現的。對於數字,每一位上的數字就是一個關鍵字,每一位的數字範圍就是關鍵字範圍,它的主要過程為:將所有待比較數值(正整數)統一為同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後,數列就變成一個有序序列,如下圖所示。類似從低位到高位比較,就是從次關鍵字到主關鍵字比較,這種稱為最低位優先(LSD),反之稱為最高位優先(MSD)。

基數排序的程式碼實現:

#include <stdio.h>
#include <stdlib.h>

int n;    //元素個數
int bit_num;    //最大數字位數

/*
 * 獲取相應位置上的數(從右到左)
 */
int GetNumInPos(int num, int pos)
{
    int i, temp = 1;
    for (i = 0; i < pos - 1; i++)
    {
        temp *= 10;
    }
    return (num / temp) % 10;
}

/*
 * 基數排序(LSD)
 */
void RadixSort(int *array)
{
    int radix = 10;
    int *count, *bucket, i, j, k;
    count = (int*) malloc(sizeof(int) * radix);
    bucket = (int*) malloc(sizeof(int) * n);
    for (k = 1; k <= bit_num; k++)
    {
        for (i = 0; i < radix; i++)
        {
            count[i] = 0;
        }
        //統計各個桶中所盛資料個數
        for (i = 0; i < n; i++)
        {
            count[GetNumInPos(array[i], k)]++;
        }
        //count[i]表示第i個桶的右邊界索引
        for (i = 1; i < radix; i++)
        {
            count[i] = count[i] + count[i - 1];
        }
        //分配
        for (i = n - 1; i >= 0; i--)
        {
            j = GetNumInPos(array[i], k);
            bucket[count[j] - 1] = array[i];
            count[j]--;
        }
        //收集
        for (i = 0, j = 0; i < n; i++, j++)
        {
            array[i] = bucket[j];
        }
    }
}

int main()
{
    int i;
    int *array;
    printf("請輸入最大數字的位數:");
    
            
           

相關推薦

資料結構排序

插入排序 實驗簡介 學習了前面一章的查詢,我們知道二分查詢需要先對資料進行排序,那麼這章開始我們就來講解一下幾種經典的排序演算法。 一、直接插入排序 首先我們來講直接插入排序,它的做法是:每次從無序表中取出第一個元素,把它插入到有序表的合適位置,使有序表仍然有序。第一趟

資料結構排序演算法和查詢演算法的時間複雜度和空間複雜度

這是從大神給多的網站上找到的演算法的時間複雜度趨勢和各個常用結構的複雜度截圖。     演算法的時間複雜度,用來度量演算法的執行時間,記作: T(n) = O(f(n))。它表示隨著 輸入大小n 的增大,演算法執行需要的時間的增長速度可以用 f(n) 來描

2018.9.14資料結構氣泡排序

/****************************************************   @Title: 資料結構實驗   @Name: <實驗1-1> 起泡排序   @Object:      [實驗目的]         把起泡排序的演算

再談資料結構排序與查詢

1 - 引言 雖然C++中的STL庫中提供了許多排序和查詢的方法。但是我們還是需要了解一下排序和查詢內部的原理,下面讓我們學習一下各類排序與查詢演算法 2 - 歸併排序 第一種高效的排序演算法是歸併排序,按照分治三步法,對歸併排序演算法介紹如下: 劃分問題:把序列分成

資料結構 交換排序

最近只看了冒泡和快排這兩個演算法,今天面試就讓寫排序,用電腦寫,一去就讓寫程式碼,賊慌,然後面試官小姐姐真的賊好看,就是和麵試官小哥談論我的簡歷,坐在我對面,看著我寫程式碼真的賊慌。。一開始本來想寫快排,因為緊張,大腦短路,滿腦子都在冒泡泡。。最後還是寫了冒泡,,這裡再回顧一

資料結構14——插入排序、交換排序、選擇排序、歸併排序和基數排序演算法的比較

在排序過程中依據不同原則對內部排序方法進行分類,可大致分為插入排序、交換排序、選擇排序、歸併排序和基數排序等五類 插入排序 直接插入排序 直接插入排序市一中最簡單的排序方法,它的基本操作是將一個記錄插入到已排好序的有序表中,從而得到一

挖掘演算法中的資料結構:堆排序之 二叉堆Heapify、原地堆排序優化

不同於前面幾篇O(n^2)或O(n*logn)排序演算法,此篇文章將講解另一個排序演算法——堆排序,也是此係列的第一個資料結構—–堆,需要注意的是在堆結構中排序是次要的,重要的是堆結構及衍生出來的資料結構問題,排序只是堆應用之一。 此篇涉及的知識點有: 堆

演算法與資料結構2:時間複雜度——以歸併排序為例

這一篇文章我們首先會介紹一下歸併排序,並以歸併排序和我們上一章所說的插入排序為例,介紹時間複雜度。此係列的所有程式碼均可在我的 [github](https://github.com/AlbertShenC/Algorithm) 上找到。 [點此](https://github.com/AlbertShen

演算法與資料結構4:堆排序

堆排序(HeapSort)是最常用的排序演算法之一。這種排序演算法同時具有插入排序和歸併排序的優點。與插入排序一樣,具有**空間原址性**,即**任何時候都只需要常數個額外的空間儲存臨時資料**(對此,請大家回想一下歸併排序,隨著問題規模的越大,需要的額外空間就越大,在解決大型問題時,這是不可接受的缺點)。與

JavaScript 資料結構: 連結串列

前言 從實用性角度來說,連結串列對Javascript 來說沒有任何價值,為什麼呢? 我們先了解連結串列的特性,這個特性我們放在c++前提下來說,因為 這個特性是 根據 記憶體特性 來闡述的,Javascript 不存在記憶體操作,所有資料型別,本質性繼承Object 物件,而Ob

資料結構

***********************特殊的線性表-------棧**************************** 棧: 先進後出、後進先出 棧的插入運算 叫做入棧 棧的刪除運算 叫做出棧 演示程式碼: package com.chapter11; //棧的介面public int

資料結構:線性表

一、線性表及其邏輯結構 1、線性表的定義 線性表是具有相同特性的資料元素的一個有限序列。 該序列中所含的元素個數叫做線性表的長度,用 n表示(n>=0)。當 n=0時,表示線性表是一個空表,即表中不包含任何資料元素。 線性表中的第一個元素叫做表頭元素,最後一

資料結構:演算法及其描述

一、演算法及其描述 1、什麼是演算法 資料元素之間的關係有邏輯關係和物理關係,對應的操作有邏輯結構上的操作功能和具體儲存結構上的操作實現。 把 具體儲存結構上的操作實現方法 稱為演算法。 確切地說,演算法是對特定問題求解步驟的一種描述,它是指令的有限序列,其中每一

資料結構:什麼是資料結構

一、什麼是資料結構 1、資料結構的定義 資料:從計算機的角度來看,資料是所有能被輸入到計算機中且能被計算機處理的符號的集合。它是計算機操作的物件的總稱,也是計算機處理資訊的某種特定的符號表示形式(二進位制碼的抽象表示?)。 資料元素:資料元素是資料中的一個個體

20172309_《程式設計與資料結構》_課堂測試修改報告。

20172309_《程式設計與資料結構(下)》_課堂測試修改報告。 課程:《程式設計與資料結構》 班級:1723 姓名: 王志偉 學號:20172309 實驗教師:王志強老師 實驗日期:2018年6月13日 必修/選修: 必修 實驗內容: 查詢演算法綜合示例: 實驗過程及結果

資料結構之棧

棧是後進先出,先進後出 棧是一種受限制的線性表,只允許一端插入和刪除資料。 棧的實現也有兩種,用陣列實現叫順序棧;用連結串列實現叫鏈式棧。 // 基於陣列實現的順序棧 public class ArrayStack { private String[] items; // 陣列 private i

大話資料結構——棧的兩種java實現方式

在我們生活當中經常會看到這樣一種操作,比如我們往一個空羽毛球盒子裡面放羽毛球(個人比較喜歡羽毛球,嘿嘿),放完後再將羽毛球一個一個取出的時候會發現,最先放進去的羽毛球往往最後才取出來,相反,最後放入的羽毛球往往最先取出。這個例子形象的說明了棧的操作方式,下面我們來看看什麼是棧,以及棧的一些操

大話資料結構——線性表順序儲存結構的java實現

    在看《大話資料結構》的時候,裡面詼諧的語言和講解吸引了我,但是這本書是用C來實現的,但是作為一個手擼java的人就想著用java來實現一下這些資料結構,於是就有了這些大話資料結構之java實現。哈哈,感覺這樣會讓自己的理解加深不少。 &n

大話資料結構——雙向連結串列的java實現

在實現了單向連結串列後,我們在使用單向連結串列中會發現一個問題:在單向連結串列中查詢某一個結點的下一個結點的時間複雜度是O(1),但是查詢這個結點的上一個結點的時候,時間複雜度的最大值就變成了O(n),因為在查詢這個指定結點的上一個結點時又需要從頭開始遍歷。 那麼該如何解決這個困難呢?

2018-2019-20172309 《程式設計與資料結構》實驗二報告

課程:《程式設計與資料結構(下)》 班級:1723 姓名: 王志偉 學號:20172309 實驗教師:王志強老師 實驗日期:2018年11月2日 必修/選修: 必修 實驗內容: 實驗一:實現二叉樹。 1.參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,conta