基础排序算法总结(七种排序算法C代码)

排序是最基础的算法,从排序的对象来说主要分为内部排序和外部排序。内部排序主要是针对内存中的数据进行排序,外部排序针对外存如硬盘、光盘中的数据进行排序。内部排序按工作方式主要分为:插入排序(直接插入排序、希尔排序)、选择排序(简单选择排序、堆排序)、交换排序(冒泡排序、快速排序)、归并排序、基数排序。

1.直接插入排序

其基本原理就是从不断从待排序集合中选取元素,逐个插入到有序的集合中,直到取出待排序集合中全部元素。就像我们日常生活中玩扑克牌一样,一边从桌子上抽牌,一边将抽取的牌插入到手中合适的位置。其实现代码如下:

/** 
* insertSort 
* @author liebert 
* @param int arr[] 待排序数组 
* @param int len 数组长度 
*/ 
void insertSort (int arr[], int len)   
{  
    int i, j, key;  
      
    for (i = 1; i < len; i++) {  
        key = arr[i];  
        j = i - 1;  
        while (j >= 0 && key <= arr[j]) {  
            arr[j+1] = arr[j];  
            j--;  
        }  
        arr[j+1] = key;  
    }  
}  

javascript版本

function insertSort(arr){
    let len = arr.length,
        i, j, temp;

    for (i = 0; i < len - 1; i++) {
        j = i+1;
        temp = arr[j];

        while (j > 0 && temp > arr[j-1]){
            arr[j] = arr[j-1];
            j--;
        } 

        arr[j] = temp;
    }
}

2.简单选择排序

其基本工作原理是从左只右开始对集合进行扫描,集合左部分为有序集合,集合右不分为待排序集合,每次从待排序集合中选出一个最大的放入左侧有序集合中个,直到右侧待排序集合元素为空。其实现代码如下:

/** 
* selectSort 
* @author liebert 
* @param int arr[] 待排序数组 
* @param int len 数组长度 
*/  
void selectSort (int arr[], int len)   
{  
    int i, j, k, temp;  
      
    for (i = 0; i < len-1; i++) {  
        k = i;  
        for (j = i + 1; j < len; j++) {  
            if (arr[j] < arr[k]){  
                k = j;  
            }  
        }  
        if (i != k){  
            temp   = arr[i];  
            arr[i] = arr[k];  
            arr[k] = temp;  
        }  
    }  
} 

3.冒泡排序

其基本工作原理是对待排序集合进行遍历,从左至右开始,逐个元素进行扫描,如果左侧元素大于右侧(按升序)则对两个元素进行交换,直到待排序集合末尾,此时通过交换获得了最大的元素。调整右侧有序集合边界向左前进一个。其实现代码如下:

/** 
* bubbleSort 
* @author liebert 
* @param int arr[] 待排序数组 
* @param int len 数组长度 
*/  
void bubbleSort (int arr[], int len)   
{  
    int i, j, temp, flag;  
      
    for (i = 0; i < len-1; i++) {  
        flag = 0;  
        for (j = 0; j < len-i-1; j++) {  
            if(arr[j] > arr[j+1]){  
                temp     = arr[j];  
                arr[j]   = arr[j+1];  
                arr[j+1] = temp;   
                flag = 1;  
            }  
        }  
        if (0 == flag) {  
            return;  
        }  
    }  
}

4.快速排序

其基本原理是采用分治思想,将待排序集合通过中间元素划分为两个子集合,使得中间元素大于其左侧集合中的每一个元素,并且小于其右侧集合中的每一个元素。重复上述过程指导子集合中的元素个数为1。其实现代码如下:

/** 
* quickSort 
* @author liebert 
* @param int arr[] 待排序数组 
* @param int l 左边界 
* @param int r 右边界 
*/  
void quickSort (int arr[], int l, int r)   
{  
    int mid;  
    if(l < r){  
        mid = partition(arr, l, r);  
        quickSort(arr, l, mid-1);  
        quickSort(arr, mid+1, r);  
    }  
}  
  
/** 
* partition 
* @author liebert 
* @param int arr[] 待分割数组 
* @param int l 左边界 
* @param int r 右边界 
*/  
int partition (int arr[], int l, int r)   
{  
    int i, j, mid, temp;  
      
    for (i = l-1, j = l; j < r; j++){  
        if (arr[r] > arr[j]) {  
            i++;  
            if (i != j){  
                temp   = arr[j];  
                arr[j] = arr[i];  
                arr[i] = temp;  
            }  
        }  
    }  
    i++;  
    temp   = arr[r];  
    arr[r] = arr[i];  
    arr[i] = temp;  
    return i;  
} 

javascript版本:

function quickSort(arr, left, right) {
  var i = left - 1,
        j = left,
        temp;

  if(left < right) {
    for (; j < right; j++) {
      if (arr[right] > arr[j]) {
        i++;
    if (i != j) {
          temp = arr[j];
          arr[j] = arr[i];
          arr[i] = temp;
        }
      }
    }

    i++;
    temp = arr[right];
    arr[right] = arr[i];
    arr[i] = temp;

    quickSort(arr, left, i-1);
    quickSort(arr, i+1, right);
    }

    return;
}

5.归并排序

归并排序也是采用分治思想的一种排序方法。先使每个子序列有序,再使子序列段间有序,然后将已有序的子序列合并,得到完全有序的序列。其处理流程:首先对待排序集合进行划分,划分为两个较小非集合;比较a[i]和b[j]的大小,若a[i]≤b[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素b[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完;然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。其实现代码如下:

/** 
* mergeSort 
* @author liebert 
* @param int arr[] 待分割数组 
* @param int l 左边界 
* @param int r 右边界 
*/  
void mergeSort(int arr[], int result[], int l, int r)  
{  
    int i, j, mid, m;  
      
    if (l < r) {  
        mid = (l + r) / 2; // 向下取整  
        mergeSort(arr, result, l, mid);  
        mergeSort(arr, result , mid+1, r);  
        // 合并arr[l, mid]和arr[mid+1, r]  
        i = l;  
        j = mid+1;  
        m = l;  
        while (i<=mid && j<=r) {  
            if (arr[i] < arr[j]) {  
                result[m++] = arr[i++];  
            } else {  
                result[m++] = arr[j++];  
            }  
        }  
        while (i<=mid) {  
            result[m++] = arr[i++];  
        }  
        while (j<=r) {  
            result[m++] = arr[j++];  
        }  
        for(i=l; i<=r; i++) {  
            arr[i] = result[i];  
        }  
    }  
}  

6.希尔排序

希尔排序是对直接插入排序的一种改进,首先采用分治的思想缩小排序集合的规模,也就说将待排序集合进行分组,然后对分组后的集合采用插入法进行排序,通过不断缩小分组数量再排序直到分组为1,就完成了全部排序过程。

/** 
* shellSort 
* @author liebert 
* @param int arr[] 待分割数组 
* @param int len 数组长度 
*/  
void shellSort(int arr[], int len)  
{  
    int i, j, dk, temp;  
      
    // 分组  
    for (dk = len / 2; dk > 0; dk /=2) {  
        // 插入排序  
        for (i = dk; i < len; i++) {  
            temp = arr[i];  
            j = i - dk;  
            while (j >= 0 && temp < arr[j]) {  
                arr[j+dk] = arr[j];  
                j -= dk;  
            }  
            arr[j+dk] = temp;  
        }  
    }  
}  

javascript版本

function shellSort(arr){
    let len = arr.length,
        dk = Math.floor(len / 2),
        temp, i, j;

    for (; dk > 0; dk = Math.floor(dk / 2)) {
        for (i = dk; i < len; i += dk) {
            j = i;
            temp = arr[j];
            while (j > 0 && temp > arr[j-dk]) {
                arr[j] = arr[j-dk];
                j -= dk;
            }
            arr[j] = temp;
        }
    }
}

7.堆排序

堆是一个数组,在逻辑上是一个近似的完全二叉树,树上的每一个节点对应数组中的元素,数组的索引被用作树中节点的序号。对于一个序号为i的节点来说,左叶子节点为2i,右叶子节点为奇数2i+1,父节点为i/2。

堆排序的基本思路是:
① 先将初始数组R[1..n]建成一个大根堆,此堆为初始的无序区;
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key;
③由于交换后新的根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[n-1..n].keys,同样要将R[1..n-2]调整为堆。

void swap(int *a, int *b)  
{  
    int temp;  
    temp = *a;  
    *a = *b;  
    *b = temp;  
}  
/** 
* heapAdjust 
* @description 调整堆 
* @author liebert 
* @param int arr[] 待分割数组 
* @param int len 数组长度 
* @param int index 当前节点索引 
*/  
void heapAdjust(int arr[], int len, int index)  
{  
    int left  = index*2+1; // 左孩子节点索引  
    int right = index*2+2; // 右孩子节点索引  
    int large = index; // 最大节点索引  
  
    if (left <= len-1 && arr[large] < arr[left]) {  
        large = left;  
    }  
    if (right <= len-1 && arr[large] < arr[right]) {  
        large = right;  
    }  
    if (large != index){  
        swap(&arr[index], &arr[large]);  
        heapAdjust(arr, len, large);  
    }  
}  
  
/** 
* heapBuild 
* @description 创建堆 
* @author liebert 
* @param int arr[] 待分割数组 
* @param int len 数组长度 
*/  
void heapBuild(int arr[], int len)  
{  
    int i;  
      
    for (i = len / 2 - 1; i >= 0 ; i--) {  
        heapAdjust(arr, len, i);  
    }  
}  
  
/** 
* heapSort 
* @author liebert 
* @param int arr[] 待分割数组 
* @param int len 数组长度 
*/  
void heapSort(int arr[], int len)  
{  
    int i ;  
    // 建堆  
    heapBuild(arr, len);  
    // 交换  
    for (i = len; i > 1; i--) {  
        swap(&arr[0], &arr[i]);  
        // 调整堆  
        heapAdjust(arr, i-1, 0);  
    }  
      
}  

分析

(1)对于时间复杂度为O(N)的排序算法
时间复杂度为O(N)的算法主要有基数排序、计数排序,但是这两种算法都需要提前知道数组中元素的范围,以此来建立桶的数量,因此并不合适来解决这个问题。

(2)对于时间复杂度为O(N2)的排序算法
时间复杂度O(N2)常用的主要有冒泡、选择、插入,联系到题目所说的基本有序,插入排序是首选,每个元素移动的距离不超过K,因此插入排序中每个元素向前移动的距离也不会超过K,故此时插入排序的时间复杂度为O(N*K),所以插入排序可以列入考虑。

(3)对于时间复杂度为O(N*logN)的排序算法
时间复杂度O(N*logN)常用的主要有快速排序、归并排序、堆排序,因为这两种排序方法跟数组元素的初始顺序无关,因此这两种方法也是比较好的一种。而堆排序在最好、最坏、平均情况下时间复杂度都为O(N*logN)。

    原文作者:李伯特
    原文地址: https://www.jianshu.com/p/5b432442275d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞