基础排序算法总结(七种排序算法)
2017-08-08 00:31
274 查看
排序是最基础的算法。从排序的对象来说主要分为内部排序和外部排序。内部排序主要是针对内存中的数据进行排序,外部排序针对外存如硬盘、光盘中的数据进行排序。内部排序按工作方式主要分为:插入排序(直接插入排序、希尔排序)、选择排序(简单选择排序、堆排序)、交换排序(冒泡排序、快速排序)、归并排序、基数排序。
1.直接插入排序
其基本原理就是从不断从待排序集合中选取元素,逐个插入到有序的集合中,直到取出待排序集合中全部元素。就像我们日常生活中玩扑克牌一样,一边从桌子上抽牌,一边将抽取的牌插入到手中合适的位置。其实现代码如下:
2.简单选择排序
其基本工作原理是从左只右开始对集合进行扫描,集合左部分为有序集合,集合右不分为待排序集合,每次从待排序集合中选出一个最大的放入左侧有序集合中个,直到右侧待排序集合元素为空。其实现代码如下:
3.冒泡排序
其基本工作原理是对待排序集合进行遍历,从左至右开始,逐个元素进行扫描,如果左侧元素大于右侧(按升序)则对两个元素进行交换,直到待排序集合末尾,此时通过交换获得了最大的元素。调整右侧有序集合边界向左前进一个。其实现代码如下:
4.快速排序
其基本原理是采用分治思想,将待排序集合通过中间元素划分为两个子集合,使得中间元素大于其左侧集合中的每一个元素,并且小于其右侧集合中的每一个元素。重复上述过程指导子集合中的元素个数为1。其实现代码如下:
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]。其实现代码如下:
6.希尔排序
希尔排序是对直接插入排序的一种改进,首先采用分治的思想缩小排序集合的规模,也就说将待排序集合进行分组,然后对分组后的集合采用插入法进行排序,通过不断缩小分组数量再排序直到分组为1,就完成了全部排序过程。
[b]7.堆排序[/b]
堆是一个数组,在逻辑上是一个近似的完全二叉树,树上的每一个节点对应数组中的元素,数组的索引被用作树中节点的序号。对于一个序号为i的节点来说,左叶子节点为2i,右叶子节点为奇数2i+1,父节点为i/2。
堆排序的基本思路是:
① 先将初始数组R[1..n]建成一个大根堆,此堆为初始的无序区;
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R
交换,由此得到新的无序区R[1..n-1]和有序区R
,且满足R[1..n-1].keys≤R
.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]调整为堆。
分析
(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)。
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; } }
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 = 4000 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; }
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; } } }
[b]7.堆排序[/b]
堆是一个数组,在逻辑上是一个近似的完全二叉树,树上的每一个节点对应数组中的元素,数组的索引被用作树中节点的序号。对于一个序号为i的节点来说,左叶子节点为2i,右叶子节点为奇数2i+1,父节点为i/2。
堆排序的基本思路是:
① 先将初始数组R[1..n]建成一个大根堆,此堆为初始的无序区;
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R
交换,由此得到新的无序区R[1..n-1]和有序区R
,且满足R[1..n-1].keys≤R
.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 && arr[large] < arr[left]) { large = left; } if (right < len && 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)。
相关文章推荐
- Java基础学习总结(28)——Java对各种排序算法的实现
- 基础排序算法总结及实现
- 七种排序算法的总结(java版)
- Java基础-几种常见排序算法总结
- Java基础学习总结(28)——Java对各种排序算法的实现
- 基础排序算法总结
- [数据结构]九大基础排序算法总结
- 基础排序算法总结
- 数据结构与算法:七种排序算法总结(冒泡排序、选择排序、直接插入排序、希尔排序、堆排序、归并排序、快速排序)
- [Java]各种基础的查找和排序算法总结
- 基础排序算法总结
- 基础排序算法总结
- Java基础学习总结(60)——Java常用的八种排序算法
- python面试之算法基础(排序算法总结与实现)
- 七种常见经典排序算法总结(C++实现)
- 算法基础:基本排序算法原理、实现与总结
- 基础排序算法总结
- 【基础】排序算法总结
- 七种排序算法总结(冒泡、插入、选择、希尔、归并、堆、快速)
- Java基础学习总结(60)——Java常用的八种排序算法