标签:


/*** 插入排序* @param data*/public static void insertSort(int[] data){for (int i = 1; i < data.length; ++i){int curData = data[i];int j = i - 1;while (j >= 0){if (curData < data[j]){data[j + 1] = data[j];j--;}else{break;}}data[j + 1] = curData;}}/*** 在一定范围内进行插入排序, 快速排序时使用* @param data* @param left* @param right*/public static void insertSortWithBound(int[] data, int left, int right){for (int i = left + 1; i <= right; ++i){int curData = data[i];int j = i - 1;while (j >= 0){if (curData < data[j]){data[j + 1] = data[j];j--;}else{break;}}data[j + 1] = curData;}}
/*********************************************************************************************************************************/// 插入排序声明, 自己实现的, 不够简洁// 插入排序有一个特点: 那就是已经排好序的元素就是最终的元素, 利用插入排序也可以实现查找第k小的元素. 只是相对于运用优先队列来说耗时较长// 插入排序的平均和最坏时间复杂度都是O(N^2)void InsertSort(int data[], int length){int temp, i = 0, j = 0;for (i = 1; i < length; ++i){temp = data[i];while(j >= 0){if(temp < data[j]){data[j + 1] = data[j]; // 如果data[j+1]位置的元素比data[i]中的元素大的话, 就往后移动一个位置.--j;}elsebreak; // 如果data[j+1]位置的元素比data[i]中的元素小的话, 说明后面的那个空位就是data[i]的正确位置}data[j + 1] = temp; // 将data[i]放到正确的位置到}}/*********************************************************************************************************************************/
/*** 选择排序* @param data*/public static void selectSort(int[] data){for (int i = 0; i < data.length - 2; ++i){int temp = i;for (int j = i + 1; j < data.length - 1; ++j){if (data[temp] > data[j])temp = j;}swap(data, i, temp);}}
/*** 冒泡排序*/public static void bubbleSort(int[] data){for (int i = data.length - 1; i > 0; --i){int flag = 0;for (int j = 0; j < i; ++j){if (data[j] > data[j + 1]){swap(data, j, j + 1);++flag;}}// 如果在一次冒泡中一次都没有进行交换, 那么这个数组已经是有序了,这个操作会提高冒泡排序的效率if (flag == 0)break;}}
注意:希尔排序的效率依赖于增量序列的选择, 所以对于选择使用不同的增量序列进行希尔排序,效率会相差很大;通过前人的实践得出了两个效率高的两个增量序列1. 选择一个步长序列t1, t2, ....., tk, 满足ti > tj, tk = 12. 按步长序列个数k, 对待排序列进行k趟排序3. 每趟排序,根据对应的步长ti,将待排序列分割成ti个子序列, 分别对各个子序列进行插入排序4. 最后一趟的步长为1, 也就是说直接对整个数组进行了一次直接插入排序,因为经过前面的步骤后,数组中的元素已经基本有序,此时再进行一次插入排序效率会很高。
希尔排序图示:1. Hibbard增量序列: 1, 3, 7, ....., 2^k - 1; 采用此增量序列的希尔排序的最坏运行时间是N^(5/4).2. sedgewick增量序列: 4^i - 3*2^i + 1; 采用此增量序列的希尔排序的最坏运行时间是O(N^(7/6))
这个图感觉好形象的说。。。
上图是实现了序列为5, 2, 1的希尔排序的操作图示
/*** 希尔排序* 希尔排序的时间复杂度依赖于增量序列的优劣* 希尔排序最坏的时间复杂度为: O(N^2)* 采用Hibbard增量序列: 1, 3, 7, ....., 2^k - 1; 采用此增量序列的希尔排序的最坏运行时间是N^(5/4).* @param data*/public static void hillSort(int[] data){int i = 0, j = 0;int H = (1 << (int) Math.log(data.length)) - 1; // 注意:"-"的优先级比"<<"高, 如果不加括号的话,会导致运算错误for (; H >= 1; H = (H + 1) / 2 - 1){// 内部是一个以H为间隔的子数组进行了插入排序for (i = H; i < data.length; ++i){j = i - H;int temp = data[i];while (j >= 0){if (data[j] > temp){data[j + H] = data[j];j -= H;}elsebreak;}data[j + H] = temp;}}}
/*********************************************************************************************************************************//*希尔排序声明希尔排序的时间复杂度依赖于增量序列的优劣希尔排序最坏的时间复杂度为: O(N^2)采用Hibbard增量序列: 1, 3, 7, ....., 2^k - 1; 采用此增量序列的希尔排序的最坏运行时间是N^(5/4).sedgewick增量序列: 4^i - 3*2^i + 1; 采用此增量序列的希尔排序的最坏运行时间是O(N^(7/6))*/void ShellSort(int data[], int length){int i = 0, j = 0, k = 0, H = 0, temp;H = (1 << (int)_logb(length)) - 1; // 求取第一次要使用的增量值for (; H >= 1; H = (H + 1)/2 - 1){for (j = H; j < length; ++j) // 内部其实是一个插入排序{temp = data[j];while (j >= 0){if (temp < data[k]) {data[k + H] = data[k];j -= H;}elsebreak;}data[k + H] = temp;}}}/*********************************************************************************************************************************/
/*** 归并排序* 二路归并排序的时间复杂度为o(nlogn)*/public static void mergeSort(int[] data){Msort(data, 0, data.length - 1);}/*** 核心函数,通过递归实现将一个大数组分解为小数组,然后进行归并* @param data* @param leftStart* @param rightEnd*/public static void Msort(int[] data, int leftStart, int rightEnd){if (leftStart < rightEnd){int middle = (leftStart + rightEnd) / 2; // 获取中间序号Msort(data, leftStart, middle); // 先对左半部分进行归并排序Msort(data, middle + 1, rightEnd); // 在对右半部份进行归并排序merge(data, leftStart, middle + 1, rightEnd); // 将左半部分和右半部份进行合并}}/*** 合并两个数组中的数据,合并的前提是这两个数组都是有序的* @param data* @param leftStart* @param rightStart* @param rightEnd*/private static void merge(int[] data, int leftStart, int rightStart, int rightEnd){int leftEnd = rightStart - 1;int[] tempArray = new int[data.length];int ls = leftStart, rs = rightStart, ts = leftStart;// 关键步骤,通过每次比较两个数组的当前头节点来合并数组(前提是这两个数组都是有序的)while (ls <= leftEnd && rs <= rightEnd){if (data[ls] < data[rs])tempArray[ts++] = data[ls++];elsetempArray[ts++] = data[rs++];}// 左边剩下,拷贝剩余的部分while (ls <= leftEnd)tempArray[ts++] = data[ls++];// 右边剩下, 拷贝剩余的部分while (rs <= rightEnd)tempArray[ts++] = data[rs++];// 将tempArray中合并好的数组拷贝到data数组中for (int i = rightEnd; i >= leftStart; --i)data[i] = tempArray[i];}
/*********************************************************************************************************************************/// 归并排序// 归并排序的最坏时间复杂度为:O(NlogN)// 归并排序需要额外的空间. 大小等于原数组的大小N.void MergeSort(int data[], int length){void MSort(int data[], int TempArray[], int leftStart, int rightEnd);int * TempArray = NULL;TempArray = (int *)malloc(length * sizeof(int)); // 为temp数组分配空间.if (TempArray == NULL){cout << "可用空间不足, 无法进行排序" << endl;return;}else{MSort(data, TempArray, 0, length - 1);}// 释放占用的空间if (TempArray != NULL)free(TempArray);}// 归并排序的主要操作, 递归进行归并void MSort(int data[], int TempArray[], int leftStart, int rightEnd){void Merge(int data[], int TempArray[], int leftStart, int RightStart, int RightEnd);if (leftStart < rightEnd){int Middle = (leftStart + rightEnd) / 2;MSort(data, TempArray, leftStart, Middle); // 递归使用归并操作对左半数组排序MSort(data, TempArray, Middle + 1, rightEnd); // 递归归并操作对右半数组排序Merge(data, TempArray, leftStart, Middle + 1, rightEnd); // 左右半数组进行合并操作}}// 归并排序用到的低级操作// 合并操作: 将两个有序数组合并为一个// 思想等同于以前写的两个有序链表的合并算法.void Merge(int data[], int TempArray[], int leftStart, int RightStart, int RightEnd){int leftEnd = RightStart - 1;int dataNumber = RightEnd - leftStart + 1;int lS = leftStart, rS = RightStart, tS = leftStart;// 主和并循环while (lS <= leftEnd && rS <= RightEnd){if (data[lS] <= data[rS])TempArray[tS++] = data[lS++];elseTempArray[tS++] = data[rS++];}// 如果left数组剩下了, 则将剩下的部分直接复制到TempArray的后面while (lS <= leftEnd)TempArray[tS++] = data[lS++];// 如果right数组剩下了, 则将剩下的部分直接复制到TempArray的后面while (rS <= RightEnd)TempArray[tS++] = data[rS++];for (int i = RightEnd; i >= leftStart; --i)data[i] = TempArray[i];}/*********************************************************************************************************************************/
两点注意:1. 分解: 选择一个枢纽值,将输入的序列array[m...n]划分成两个非空子序列array[m...k-1]和array[k+1...n],使得array[m...k-1]中的任一元素的值都不大于枢纽值, array[k+1...n]中的任一元素的值不小于枢纽值。2. 递归:通过递归的方式对array[m...k-1]和array[k+1...n]进行快速排序。3. 递归操作完成后整个数组就成为了一个有序数组。
Java实现1. 枢纽值的选取:枢纽值的选择越能将数组平均分开越好,反之将降低快速排序的运行效率. 如果每次都以数组第一个元素作为枢纽值时, 当数组元素有序时将得到最坏的运行效率o(n^2),所以这种方法是不允许的。 这里提供两种比较好的选择枢纽值得方式:第一种就是通过随机方式选取枢纽值, 但是随机选取需要用到随机数生成,这个代价还是比较昂贵的,一般采用第二个方法:那就是采用三数中值法。将首, 中, 尾位置上的记录进行比较,选择三者的中值作为枢纽值。这样的选取方式可以很好的工作。但是这种方式要求数组必须要有三个以上的元素。2. 有如下的事实:对于小数组(N<20), 快速排序不如插入排序。所以在递归中, 如果当前的数组的元素个数小于5, 我们就采取使用插入排序的方式进行排序,这样做有两个好处,既提高了效率, 同时也满足了三数中值法的要求。
/*** 快速排序* 平均时间复杂度为:o(nlogn)* 最坏时间复杂度为:o(n^2)* @param data*/public static void quickSort(int[] data){Qsort(data, 0, data.length - 1);}/*** 快速排序的核心函数* 下面的算法采用了三数中值分割法, 尽可能好的选择了枢纽.* 快速排序需要通过递归来实现,其最大的深度为logn + 1(向上取整), 所以空间复杂度应为logn* @param data* @param left* @param right*/public static void Qsort(int[] data, int left, int right){if (right - left > 5){ // 判断当前数组的大小, 如果当前数组过小, 则使用插入排序, 因为此时插入排序比快速排序要快int pivot = middle3(data, left, right);int i = left, j = pivot; // 因为最右边的那个数已经符合要求啦(因为在middle3中会使得最右边的那个数大于枢纽值)for (;;){while (data[++i] < data[pivot]); // 从左向右遍历元素, 遇到比枢纽值大的元素就在此停下来等待互换while (data[--j] > data[pivot]); // 从右向左遍历元素, 遇到比枢纽值小的元素就在此停下来等待互换if (i < j){ // 检查i 是否已经越过 j, 没有越过则需要交换两个位置上的值swap(data, i, j);}else{break;}}swap(data, i, pivot); // 将枢纽还原到原来的正确位置, 因为i在结束时肯定指向的是一个大于枢纽值的元素, 所以可以直接互换Qsort(data, left, i - 1); // 左半边递归快排Qsort(data, i + 1, right); // 右半边递归快排}else{// 当数组的个数小于3时,使用插入排序比使用快速排序效率更高insertSortWithBound(data, left, right);}}/*** 三位中值分割法, 用于选取合适的枢纽, 是快速排序效率更高(相比于直接使用0号元素作为枢纽来进行排序, 三位中值分割法能更好的选取合适的枢纽值)* 从头, 中, 尾三个元素中选出中间元素作为枢纽, 并将他们排序。返回值是枢纽元素所在的位置* @param data* @param left* @param right* @return*/private static int middle3(int[] data, int left, int right){// 算出中间序号int middle = (left + right) / 2;// 将最前, 中间, 最后三个位置上的数据进行排序if (data[left] > data[middle])swap(data, left, middle);if (data[left] > data[right])swap(data, left, right);if (data[middle] > data[right])swap(data, middle, right);// 将中间位置上的数据(枢纽值)和right-1位置的数据交换位置swap(data, middle, right - 1);return right - 1; // 交换位置后,right - 1 位置上的值才是枢纽值。}
/*********************************************************************************************************************************/// 快速排序// 有以下的事实: 对于很小的数组(N <= 20), 快速排序不如插入排序好.// 下面的算法采用了三数中值分割法, 尽可能好的选择了枢纽.// 快速排序的平均情况的时间复杂度为: O(NlogN).// 快速排序的最好情况的时间复杂度为: O(NlogN).// 快速排序的最坏情况的时间复杂度为:O(N^2).#define TRESHOLD 3void QuickSort(int data[],int left, int right){int Middle3(int data[], int left, int right);void Swap(int* x, int* y);//*1*if (left + TRESHOLD <= right) // 判断当前数组的大小, 如果当前数组过小, 则使用插入排序, 因为此时插入排序比快速排序要快{int pivot = Middle3(data, left, right); // 只有在进行快排的时候才需要进行此操作, 在进行插入排序的时候则不需要此排序(如果在执行插入排序时进行了此操作会出错, 所以这个操作一定要放到if语句内部, 而不能放在外部, 即*1*处)int i = left, j = pivot;for (;;){while (data[++i] < data[pivot]) ; // 从左向右遍历元素, 遇到比枢纽值大的元素就在此停下来等待互换while (data[--j] >data[pivot]) ; // 从右向左遍历元素, 遇到比枢纽值小的元素就在此停下来等待互换if (i > j) // 检查i 是否已经越过 j, 如果越过证明已经遍历完成{Swap(&data[i], &data[pivot]); // 将枢纽还原到原来的正确位置, 因为i在结束时肯定指向的是一个大于枢纽值的元素, 所以可以直接互换break;}Swap(&data[i], &data[j]); // 如果i仍然小于j, 那么就需要将i停在的位置的元素和j停在的位置的元素进行互换. 让他们都在合适的位置}QuickSort(data, left, i - 1); // 左半边递归快排QuickSort(data, i + 1, right); // 有半边递归快排}else{InsertSort(data + left, right - left + 1); // 当数组的个数不大于3时,使用插入排序, 因为小数组时时插入比快排更快}}// 三位中值分割法, 用于选取合适的枢纽, 是快速排序效率更高(相比于直接使用0号元素作为枢纽来进行排序, 三位中值分割法能更好的选取合适的枢纽值)// 从头, 中, 尾三个元素中选出中间元素作为枢纽, 并将他们排序// 返回值是枢纽元素所在的位置int Middle3(int data[], int left, int right){void Swap(int* x, int* y);int middle = (left + right)/2;// 将这三个位置上的元素按照从小到大的顺序排序if (data[left] > data[middle])Swap(&data[left], &data[middle]);if (data[left] > data[right])Swap(&data[left], &data[right]);if (data[middle] > data[right])Swap(&data[middle], &data[right]);// 将middle位置的枢纽和right - 1位置的元素互换// 互换之后, right - 1的位置上的元素就是枢纽int temp = data[middle];data[middle] = data[right - 1];data[right - 1] = temp;return right - 1; // 返回middle位置}// 交换两个位置上的元素函数void Swap(int* x, int* y){int temp = *x;*x = *y;*y = temp;}/*********************************************************************************************************************************/
/*** 堆排序* 堆排序的原理是先构建一个Max堆(大顶堆), 然后通过删除大顶堆的最大的节点(将根节点依次和最后一个节点进行互换位置进行删除操作).* 最后排序完成(最后是升序排列的数据).* 最坏时间复杂度: NlogN* 平均时间复杂度: NlogN* @param data*/public static void heapSort(int[] data){// 首先将数组调整成为大顶堆int length = data.length;for (int i = length/2 - 1; i >= 0; --i){ // length/2 - 1的位置是最后一个有孩子的节点的位置percolateDown(data, i, length - 1);}// 将大顶堆的顶点值和最后一个位置的值互换,然后再次进行下滤操作从剩余的数组中选出最大值, 重复该操作for (int i = length - 1; i >= 0; --i){swap(data, 0, i);percolateDown(data, 0, i - 1);}}/*** 下滤操作, 堆排序所需的基本操作, 这个需要有完全二叉树的基本知识, 建议看一下树那一章。* @param data* @param i* @param N*/public static void percolateDown(int[] data, int i, int N){int temp, child;for (temp = data[i]; leftChild(i) <= N; i = child){child = leftChild(i);if (child != N && data[child] < data[child + 1]){ // 如果有右孩子并且右孩子的值大于左孩子的值, 那么指向右孩子, 否则什么也不做child++;}if (data[child] > temp){ // 最大的孩子和temp(爹)中的值进行比较,如果孩子的值比temp大,则爹和孩子互换位置, 如果孩子的值比temp小, 则停止循环data[i] = data[child];}else{break;}}data[i] = temp; // 将temp中的值赋给当前的位置}/*** 计算左孩子的位置,因为数组的下标是从0开始的,所以计算左孩子下表的计算方法会和以1开始的数组的计算方式不太一样* @param i* @return*/private static int leftChild(int i) {return 2*i+ 1;}
/*********************************************************************************************************************************/// 堆排序// 堆排序的原理是先构建一个Max堆(大顶堆), 然后通过删除大顶堆的最大的节点(将根节点依次和最后一个节点进行互换位置进行删除操作.// 最后排序完成(最后是升序排列的数据).// 最坏时间复杂度: NlogN// 平均时间复杂度: NlogNvoid HeapSort(int data[], int length){void PercolateDown(int data[], int i, int length);// 第一步, 通过下滤操作构建Max堆for(int i = length/2; i >= 0; --i)PercolateDown(data, i, length);cout << "完成Max堆的构建" << endl;for (int j = length - 1; j > 0; --j) // 每次都将堆顶元素和当前堆的最后一个元素交换, 然后再次调整堆使之成为大顶堆{int temp = data[j];data[j] = data[0];data[0] = temp;PercolateDown(data, 0, j);}}// 堆排序所需要的下滤操作#define LeftChild(i) ((2*i)+1) // 因为数组是以0开头的, 所以左孩子是2 * i + 1, 而右孩子是2 * i+ 2void PercolateDown(int data[], int i, int length){int temp, child;for (temp = data[i]; (child = LeftChild(i)) < length; i = child){if (child != length - 1 && data[child] < data[child + 1]) // 因为for中已经判断了child是小于length的, 所以在此只通过判断child是否等于length - 1就能判断出这个节点有没有右孩子++child;if (temp < data[child]) // 如果孩子中有比爹大的, 则孩子当爹.data[i] = data[child];else // 俩孩子都没有爹大break;}data[i] = temp; // 下滤操作完成, 将temp填入合适的位置}/*********************************************************************************************************************************/
/*********************************************************************************************************************************/// 桶式排序// 桶式排序的要求比较苛刻, 它要求输入的数据必须只由小于max的正整数组成// 桶式排序的原理很简单, 而且时间复杂度是线性的, 但是它对空间量需求比其它的排序算法要高的多.// 桶式排序的平均时间复杂度是O(N)void BucketSort(int data[], int length, int max){int* temp = (int* )malloc(sizeof(int) * max);if (temp == NULL){cout << "内存空间不足" << endl;return ;}// 初始化temp数组for (int i = 0; i < max; ++i)temp[i] = 0;// 统计data数组中的元素for (int i = 0; i < length; ++i)temp[data[i]] += 1; // +=是考虑到可能会有重复的元素for (int i = 0, j = 0; i < max; ++i)while (temp[i]-- != 0){data[j++] = i;}// 释放占用的空间if (temp != NULL)free(temp);}/*********************************************************************************************************************************/
| 排序算法 | 最好时间 | 平均时间 | 最坏时间 | 辅助存储 | 稳定性 | 备注 |
| 简单选择排序 | o(n2) | o(n2) | o(n2) | o(1) | 不稳定 | n小时较好 |
| 直接插入排序 | o(n) | o(n2) | o(n2) | o(1) | 稳定 | 基本有序时较好 |
| 冒泡排序 | o(n)(优化后) | o(n2) | o(n2) | o(1) | 稳定 | n小时较好, 基本有序时较好 |
| 希尔排序 | o(n) | o(nlogn) | o(ns)(1<s<2) | o(1) | 不稳定 |
希尔排序在使用不同的增量序列进行排序时, 其时间复杂度差别较大 s对于选择的不同的增量序列其最坏时间不尽相同 |
| 快速排序 | o(nlogn) | o(nlogn) | o(n2) | o(logn)(因为递归的原因) | 不稳定 |
使用三数中值分割法和小数组使用插入排序对快速排序进行优化, 使得快速排序得到很大的优化 待排数组越无序越好 |
| 堆排序 | o(nlogn) | o(nlogn) | o(nlogn) | o(1) | 不稳定 | |
| 归并排序 | o(nlogn) | o(nlogn) | o(nlogn) | o(n) (需要一个辅助数组) | 稳定 |
| 排序算法 | 平均情况下时间复杂度 | 最坏情况下时间复杂度 | 备注 | ||
| 插入排序 | O(N2) | O(N2) | |||
| 希尔排序 | Hibbard增量序列 | O(N5/4) | O(N3/2) | 希尔排序在使用不同的增量序列进行排序时, 其时间复杂度差别较大 | |
| Sedgewick增量序列 | O(N7/6) | O(N4/3) | |||
| 堆排序 | O(NlogN) | O(NlogN) | 使用二叉堆, 先构建Max二叉堆, 然后使用DeleteMin方法每次将最大的数从队列中删除然后放到数组的尾部, 当执行N次后排序完成. | ||
| 归并排序 | O(NlogN) | O(NlogN) | 递归算法的优秀运用 | ||
| 快速排序 | O(NlogN) |
O(N2) |
使用三数中值分割法和小数组使用插入排序对快速排序进行优化, 使得快速排序得到很大的优化 |
||
| 桶式排序 | O(N) | - | 该算法使用限制较多, 但是在限制之下, 此算法可以以线性时间进行排序工作 | ||
标签:
原文地址:http://blog.csdn.net/wangmengdeboke/article/details/51897294