十种排序算法

排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括: 

排序是否是稳定的看什么呢?

排序算法的稳定性是指在排序过程中保持相等元素的相对顺序不变。简单来说,如果一个排序算法能够保证相等元素的顺序不发生改变,那么它就是稳定的。

1. 冒泡排序

​冒泡排序(Bubble Sort)也是一种简单直观的排序算法。假设长度为n的数组arr,要按照从小到大排序。则冒泡排序的具体过程可以描述为:首先从数组的第一个元素开始到数组最后一个元素为止,对数组中相邻的两个元素进行比较,如果位于数组左端的元素大于数组右端的元素,则交换这两个元素在数组中的位置。这样操作后数组最右端的元素即为该数组中所有元素的最大值接着对该数组除最右端的n-1个元素进行同样的操作,再接着对剩下的n-2个元素做同样的操作,直到整个数组有序排列。算法的时间复杂度为O(n^2)。

冒泡排序算法的原理如下:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

核心代码如下:

void BubbleSort(int arr[], int length){
// arr:需要排序的数组 length:数组长度 注:int cnt = sizeof(a)/sizeof(a[0]);获取数组长度
	for (int i = 0; i < length; i++){
		for (int j = 0; j < length -  i - 1; j++){
			if (arr[j] > arr[j + 1]){
				int temp;
				temp = arr[j + 1];
				arr[j + 1] = arr[j];
				arr[j] = temp;
			}
		}
	}
}

2. 选择排序

选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间。具体来说,假设长度为n的数组arr,要按照从小到大排序,那么先从n个数字中找到最小值min1,如果最小值min1的位置不在数组的最左端(也就是min1不等于arr[0]),则将最小值min1和arr[0]交换,接着在剩下的n-1个数字中找到最小值min2,如果最小值min2不等于arr[1],则交换这两个数字,依次类推,直到数组arr有序排列。算法的时间复杂度为O(n^2)。

选择排序算法的原理如下:

  1. 首先在未排序序列中找到最小元素,存放到排序序列的起始位置。
  2. 再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。
  3. 重复第二步,直到所有元素均排序完毕。

核心代码如下:

// 自定义方法:交换两个变量的值
void swap(int *a,int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
/* 选择排序 */
void selection_sort(int arr[], int len){
    int i,j;
    for (i = 0 ; i < len - 1 ; i++) {
        int min = i;
        for (j = i + 1; j < len; j++) {    //  遍历未排序的元素
            if (arr[j] < arr[min]) {min = j;}
        }
        swap(&arr[min], &arr[i]);    //做交换
        /*if (index != i){ // 不用自定义函数时可以用选择下面方式进行交换
			int temp = arr[i];
			arr[i] = arr[index];
			arr[index] = temp;
		}*/
    }
}

3. 插入排序

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。例如要将数组arr=[4,2,8,0,5,1]排序,可以将4看做是一个有序序列,将[2,8,0,5,1]看做一个无序序列。无序序列中2比4小,于是将2插入到4的左边,此时有序序列变成了[2,4],无序序列变成了[8,0,5,1]。无序序列中8比4大,于是将8插入到4的右边,有序序列变成了[2,4,8],无序序列变成了[0,5,1]。以此类推,最终数组按照从小到大排序。该算法的时间复杂度为O(n^2)。

插入排序算法的原理如下:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. ​将新元素插入到该位置后;
  6. 重复步骤2~5。

核心代码如下:

/* 插入排序 */
void insertion_sort(int arr[], int len){
    int i,j,key;
    for (i=1;i<len;i++){
        key = arr[i];
        j=i-1;
        while((j>=0) && (arr[j]>key)) {
            arr[j+1] = arr[j];
            j--;
        }
        arr[j+1] = key;   // j不满足>=0条件跳出后j=-1,所以此时arr[j+1]其实是arr[0]
    }
}

4. 希尔排序

希尔排序(Shell’s Sort)在插入排序算法的基础上进行了改进,算法的时间复杂度与前面几种算法相比有较大的改进,但希尔排序是非稳定排序算法。其算法的基本思想是:先将待排记录序列分割成为若干子序列分别进行插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行一次直接插入排序。该算法时间复杂度为O(n log n)。

核心代码如下:

/*希尔排序*/
void shell sort(int arr[], int n){
    int i,j, inc, key;
    // 初始增量:n/2,每一趟之后除以二
    for(inc=n/2;inc>0;inc /=2){
    // 每一趟采用插入排序
        for(i=inc;i<n;i++){
            key = arr[i];
            for(j=i; j>=inc && key< arr[j-inc]; j-= inc){
                arr[j]= arr[j-inc];
                arr[j] = key;
            }
        }
    }
}

5. 归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。代价是需要额外的内存空间。若将两个有序表合并成一个有序表,称为2路归并。 该算法时间复杂度为O(n log n)。

// 合并数组
void merge(int arr[], int tempArr[], int left, int mid, int right){
    // 标记左半区第一个未排序的元素
    int l_pos = left;
    // 标记右半区第一个未排序的元素
    int r_pos = mid + 1;
    // 临时数组元素的下标
    int pos = left;
    // 合并
    while(l_pos <= mid && r_pos <= right){
        if(arr[l_pos]<= arr[r_pos]) // 左半区第一个剩余元素更小
            tempArr[pos++]= arr[l_pos++];
        else    // 右半区第一个剩余元素更小
            tempArr[pos++]= arr[r_pos++];
    }
    // 合并左半区剩余的元素
    while(l_pos <= mid){
        tempArr[pos++]= arr[l_pos++];
    }
    // 合并右半区剩余的元素
    while(r_pos <= right){
        tempArr[pos++]= arr[r_pos++];
    }
    // 把临时数组中合并后的元素复制回原来的数组
    while(left<=right){
        arr[left]=tempArr[left];
        left++;
    }
}

// 归并排序 tempArr是临时用来存储归并完了的数组
void msort(int arr[],int tempArr[], int left, int right){
    //如果只有一个元素,那么就不需要继续划分
    //只有一个元素的区域,本身就是有序的,只需要被归并即可
    if(left< right){
        int mid=(left + right)/2;    // 找中间点
        // 递归划分左半区和右半区
        msort(arr,tempArr, left, mid);
        msort(arr, tempArr, mid + 1,right);
        merge(arr,tempArr, left, mid, right);  // 合并已经排序的部分
    }
}

// 归并排序入口
void merge_sort(int arr[], int n){
    // 分配一个辅助数组
    int *tempArr =(int *)malloc(n*sizeof(int));
    if(tempArr){    // 辅助数组分配成功
        msort(arr,tempArr,0,n-1);
        free(tempArr);
    }
    else{printf("error: failed to allocate memory");}
}

6. 快速排序

快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。快速排序的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,已达到整个序列有序。一趟快速排序的具体过程可描述为:从待排序列中任意选取一个记录(通常选取第一个记录)作为基准值,然后将记录中关键字比它小的记录都安置在它的位置之前,将记录中关键字比它大的记录都安置在它的位置之后。这样,以该基准值为分界线,将待排序列分成的两个子序列。它是处理大数据最快的排序算法之一。该算法时间复杂度为O(n log n)。

快速排序算法的原理如下:

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

核心代码如下:

void QuickSort(int arr[], int start, int end){
	if (start >= end) return;
	int i = start;
	int j = end;
	// 基准数
	int baseval = arr[start];
	while (i < j){
		// 从右向左找比基准数小的数
		while (i < j && arr[j] >= baseval){j--;}
		if (i < j){
			arr[i] = arr[j];
			i++;
		}
		// 从左向右找比基准数大的数
		while (i < j && arr[i] < baseval){i++;}
		if (i < j){
			arr[j] = arr[i];
			j--;
		}
	}
	// 把基准数放到i的位置
	arr[i] = baseval;
	QuickSort(arr, start, i - 1);
	QuickSort(arr, i + 1, end);
}

7. 堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:每个结点的值都大于等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于等于其左右孩子结点的值,称为小顶堆。该算法时间复杂度为O(n log n)。

堆排序算法的原理如下:

  1. 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  2. 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  3. 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

代码如下:

 参考常用十大排序算法-CSDN博客 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值