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

来源:互联网 发布:java构造器小技巧 编辑:程序博客网 时间:2024/06/06 07:28

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

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 = 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;}}}

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 && 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; 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)。


原创粉丝点击