可以在网上接网站做的网址县科协微网站建设
web/
2025/9/30 13:28:44/
文章来源:
可以在网上接网站做的网址,县科协微网站建设,做带支付功能的网站,衡阳做网站的我对java的八大排序算法进行了总结#xff0c;以此文展示Java八大算法
常见排序算法如下#xff1a;
1.直接插入排序 2.希尔排序 3.简单选择排序 4.堆排序 5.冒泡排序 6.快速排序 7.归并排序 8.基数排序
排序方法示例简介
直接插入排序
基本思想 通常人们整理桥牌的方法…我对java的八大排序算法进行了总结以此文展示Java八大算法
常见排序算法如下
1.直接插入排序 2.希尔排序 3.简单选择排序 4.堆排序 5.冒泡排序 6.快速排序 7.归并排序 8.基数排序
排序方法示例简介
直接插入排序
基本思想 通常人们整理桥牌的方法是一张一张的来将每一张牌插入到其他已经有序的牌中的适当位置。在计算机的实现中为了要给插入的元素腾出空间我们需要将其余所有元素在插入之前都向右移动一位。
算法描述 一般来说插入排序都采用in-place在数组上实现。具体算法描述如下 从第一个元素开始该元素可以认为已经被排序 取出下一个元素在已经排序的元素序列中从后向前扫描 如果该元素已排序大于新元素将该元素移到下一位置 重复步骤3直到找到已排序的元素小于或者等于新元素的位置 将新元素插入到该位置后 重复步骤2~5
注意 如果 比较操作 的代价比 交换操作 大的话可以采用二分查找法来减少 比较操作 的数目。该算法可以认为是 插入排序 的一个变种称为二分查找插入排序。 代码实现
/*** 通过交换进行插入排序借鉴冒泡排序** param a*/
public static void sort(int[] a) {for (int i 0; i a.length - 1; i) {for (int j i 1; j 0; j--) {if (a[j] a[j - 1]) {int temp a[j];a[j] a[j - 1];a[j - 1] temp;}}}
}/*** 通过将较大的元素都向右移动而不总是交换两个元素** param a*/
public static void sort2(int[] a) {for (int i 1; i a.length; i) {int num a[i];int j;for (j i; j 0 num a[j - 1]; j--) {a[j] a[j - 1];}a[j] num;}
}复杂度分析
直接插入排序复杂度如下 平均时间复杂度 O(n²) 最好情况 O(n²) 最坏情况 O(n²) 空间复杂度 O(1)
比较与总结 插入排序所需的时间取决于输入元素的初始顺序。例如对一个很大且其中的元素已经有序(或接近有序)的数组进行排序将会比随机顺序的数组或是逆序数组进行排序要快得多。
希尔排序
希尔排序也称 递减增量排序算法是插入排序的一种更高效的改进版本。希尔排序是 非稳定排序算法。 希尔排序是基于插入排序的以下两点性质而提出改进方法的 插入排序在对几乎已经排好序的数据操作时效率高即可以达到线性排序的效率 但插入排序一般来说是低效的因为插入排序每次只能将数据移动一 希尔排序是先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序待整个序列中的记录“基本有序”时再对全体记录进行依次直接插入排序。
基本思想 将待排序数组按照步长gap进行分组然后将每组的元素利用直接插入排序的方法进行排序每次再将gap折半减小循环上述操作当gap1时利用直接插入完成排序。 可以看到步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。一般来说最简单的步长取值是初次取数组长度的一半为增量之后每次再减半直到增量为1。更好的步长序列取值可以参考维基百科。
算法描述 选择一个增量序列 t1t2……tk其中 ti tj, tk 1 按增量序列个数 k对序列进行 k 趟排序 每趟排序根据对应的增量 ti将待排序列分割成若干长度为 m 的子序列分别对各子表进行直接插入排序。仅增量因子为 1 时整个序列作为一个表来处理表长度即为整个序列的长度。
代码实现 下面参考《算法》中给出的步长选择策略《算法》中给出的解释是 下面代码中递增序列的计算和使用都很简单和复杂递增序列的性能接近。当可以证明复杂的序列在最坏情况下的性能要好于我们所使用的递增序列。更加优秀的递增序列有待我们去发现。
public static void sort(int[] a) {int length a.length;int h 1;while (h length / 3) h 3 * h 1;for (; h 1; h / 3) {for (int i 0; i a.length - h; i h) {for (int j i h; j 0; j - h) {if (a[j] a[j - h]) {int temp a[j];a[j] a[j - h];a[j - h] temp;}}}}
}复杂度分析
以下是希尔排序复杂度: 平均时间复杂度 O(nlog2 n) 最好情况 O(nlog2 n) 最坏情况 O(nlog2 n) 空间复杂度 O(1)
总结与思考 希尔排序更高效的原因是它权衡了子数组的规模和有序性。排序之初各个子数组都很短排序之后子数组都是部分有序的这两种情况都很适合插入排序。
#### 简单选择排序 基本思想 选择排序Selection sort是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小大元素存放到排序序列的起始位置然后再从剩余未排序元素中继续寻找最小大元素然后放到已排序序列的末尾。以此类推直到所有元素均排序完毕。 选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上则它不会被移动。选择排序每次交换一对元素它们当中至少有一个将被移到其最终位置上因此对 n个元素的表进行排序总共进行至多 n-1 次交换。在所有的完全依靠交换去移动元素的排序方法中选择排序属于非常好的一种。
算法描述 从未排序序列中找到关键字最小的元素 如果最小元素不是未排序序列的第一个元素将其和未排序序列第一个元素互换 重复1、2步直到排序结束。
代码实现
public static void sort(int[] a) {for (int i 0; i a.length; i) {int min i;//选出之后待排序中值最小的位置for (int j i 1; j a.length; j) {if (a[j] a[min]) {min j;}}//最小值不等于当前值时进行交换if (min ! i) {int temp a[i];a[i] a[min];a[min] temp;}}
}复杂度分析
以下是选择排序复杂度: 平均时间复杂度 O(n²) 最好情况 O(n²) 最坏情况 O(n²) 空间复杂度 O(1)
总结与思考 选择排序的简单和直观名副其实这也造就了它”出了名的慢性子”无论是哪种情况哪怕原数组已排序完成它也将花费将近n²/2次遍历来确认一遍。即便是这样它的排序结果也还是不稳定的。 唯一值得高兴的是它并不耗费额外的内存空间。
堆排序
1991年的计算机先驱奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德(Robert WFloyd) 和威廉姆斯(JWilliams) 在1964年共同发明了著名的堆排序算法(Heap Sort). 堆的定义如下 n个元素的序列{k1,k2,…,kn} 当且仅当满足下关系时称之为堆。
把此序列对应的二维数组看成一个完全二叉树。那么堆的含义就是完全二叉树中任何一个非叶子节点的值均不大于或不小于其左右孩子节点的值。 由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的小顶堆的堆顶的关键字是所有关键字中最小的。因此我们可使用大顶堆进行升序排序, 使用小顶堆进行降序排序。
基本思想 此处以大顶堆为例堆排序的过程就是将待排序的序列构造成一个堆选出堆中最大的移走再把剩余的元素调整成堆找出最大的再移走重复直至有序。
代码实现 从算法描述来看堆排序需要两个过程一是建立堆二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆函数二是反复调用建堆函数以选择出剩余未排元素中最大的数来实现排序的函数。
总结起来就是定义了以下几种操作 最大堆调整Max_Heapify将堆的末端子节点作调整使得子节点永远小于父节点 创建最大堆Build_Max_Heap将堆所有数据重新排序 堆排序HeapSort移除位在第一个数据的根节点并做最大堆调整的递归运算 对于堆节点的访问 父节点i的左子节点在位置(2i1); 父节点i的右子节点在位置(2i2); 子节点i的父节点在位置floor((i-1)/2);
/*** param a*/
public static void sort(int[] a) {for (int i a.length - 1; i 0; i--) {max_heapify(a, i);//堆顶元素(第一个元素)与Kn交换int temp a[0];a[0] a[i];a[i] temp;}
}/***** 将数组堆化* i 第一个非叶子节点。* 从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。* 叶子节点可以看作已符合堆要求的节点根节点就是它自己且自己以下值为最大。** param a* param n*/
public static void max_heapify(int[] a, int n) {int child;for (int i (n - 1) / 2; i 0; i--) {//左子节点位置child 2 * i 1;//右子节点存在且大于左子节点child变成右子节点if (child ! n a[child] a[child 1]) {child;}//交换父节点与左右子节点中的最大值if (a[i] a[child]) {int temp a[i];a[i] a[child];a[child] temp;}}
}总结与思考 由于堆排序中初始化堆的过程比较次数较多, 因此它不太适用于小序列。 同时由于多次任意下标相互交换位置, 相同元素之间原本相对的顺序被破坏了, 因此, 它是不稳定的排序。
冒泡排序
我想对于它每个学过C语言的都会了解这可能是很多人接触的第一个排序算法。 基本思想 冒泡排序Bubble Sort是一种简单的排序算法。它重复地走访过要排序的数列一次比较两个元素如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。 算法描述 冒泡排序算法的运作如下 比较相邻的元素。如果第一个比第二个大就交换他们两个。 对每一对相邻元素作同样的工作从开始第一对到结尾的最后一对。这步做完后最后的元素会是最大的数。 针对所有的元素重复以上的步骤除了最后一个。 持续每次对越来越少的元素重复上面的步骤直到没有任何一对数字需要比较。
代码实现
public static void sort(int[] a) {//外层循环控制比较的次数for (int i 0; i a.length - 1; i) {//内层循环控制到达位置for (int j 0; j a.length - i - 1; j) {//前面的元素比后面大就交换if (a[j] a[j 1]) {int temp a[j];a[j] a[j 1];a[j 1] temp;}}}
}复杂度分析 以下是冒泡排序算法复杂度: 平均时间复杂度 O(n²) 最好情况 O(n) 最坏情况 O(n²) 空间复杂度 O(1) 冒泡排序是最容易实现的排序, 最坏的情况是每次都需要交换, 共需遍历并交换将近n²/2次, 时间复杂度为O(n²). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有缓存的temp变量需要内存空间, 因此空间复杂度为常量O(1).
总结与思考 由于冒泡排序只在相邻元素大小不符合要求时才调换他们的位置, 它并不改变相同元素之间的相对顺序, 因此它是稳定的排序算法。
快速排序
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较但这种状况并不常见。事实上快速排序通常明显比其他 Ο(nlogn) 算法更快因为它的内部循环inner loop可以在大部分的架构上很有效率地被实现出来。
基本思想 快速排序的基本思想挖坑填数分治法。 快速排序使用分治法Divide and conquer策略来把一个串行list分为两个子串行sub-lists。 快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看快速排序应该算是在冒泡排序基础上的递归分治法。 快速排序的名字起的是简单粗暴因为一听到这个名字你就知道它存在的意义就是快而且效率高它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²)但是人家就是优秀在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好。
算法描述 快速排序使用分治策略来把一个序列list分为两个子序列sub-lists。步骤为 从数列中挑出一个元素称为基准pivot。 重新排序数列所有比基准值小的元素摆放在基准前面所有比基准值大的元素摆在基准后面相同的数可以到任一边。在这个分区结束之后该基准就处于数列的中间位置。这个称为分区partition操作。 递归地recursively把小于基准值元素的子数列和大于基准值元素的子数列排序。 递归到最底部时数列的大小是零或一也就是已经排序好了。这个算法一定会结束因为在每次的迭代iteration中它至少会把一个元素摆到它最后的位置去。
代码实现 用伪代码描述如下 i L; j R; 将基准数挖出形成第一个坑a[i]。 j–由后向前找比它小的数找到后挖出此数填前一个坑a[i]中。 i由前向后找比它大的数找到后也挖出此数填到前一个坑a[j]中。 再重复执行23二步直到ij将基准数填入a[i]中
public static void sort(int[] a, int low, int high) {//已经排完if (low high) {return;}int left low;int right high;//保存基准值int pivot a[left];while (left right) {//从后向前找到比基准小的元素while (left right a[right] pivot)right--;a[left] a[right];//从前往后找到比基准大的元素while (left right a[left] pivot)left;a[right] a[left];}// 放置基准值准备分治递归快排a[left] pivot;sort(a, low, left - 1);sort(a, left 1, high);
}上面是递归版的快速排序通过把基准插入到合适的位置来实现分治并递归地对分治后的两个划分继续快排。那么非递归版的快排如何实现呢 因为 递归的本质是栈 所以我们非递归实现的过程中可以借助栈来保存中间变量就可以实现非递归了。在这里中间变量也就是通过Pritation函数划分区间之后分成左右两部分的首尾指针只需要保存这两部分的首尾指针即可。
public static void sortByStack(int[] a) {StackInteger stack new StackInteger();//初始状态的左右指针入栈stack.push(0);stack.push(a.length - 1);while (!stack.isEmpty()) {//出栈进行划分int high stack.pop();int low stack.pop();int pivotIndex partition(a, low, high);//保存中间变量if (pivotIndex low) {stack.push(low);stack.push(pivotIndex - 1);}if (pivotIndex high pivotIndex 0) {stack.push(pivotIndex 1);stack.push(high);}}
}private static int partition(int[] a, int low, int high) {if (low high) return -1;int left low;int right high;//保存基准的值int pivot a[left];while (left right) {//从后向前找到比基准小的元素插入到基准位置中while (left right a[right] pivot) {right--;}a[left] a[right];//从前往后找到比基准大的元素while (left right a[left] pivot) {left;}a[right] a[left];}//放置基准值准备分治递归快排a[left] pivot;return left;
}算法改进 切换到插入排序 和大多数递归排序算法一样改进快速排序性能的一个简单方法基于以下两点 对于小数组快速排序比插入排序慢 因为递归快速排序的sort()方法在小数组中叶会调用自己 因此在排序小数组时应该切换到插入排序。
三者取中法 快速排序是通常被认为在同数量级O(nlog2n)的排序方法中平均性能最好的。但若初始序列按关键码有序或基本有序时快排序反而蜕化为冒泡排序。为改进之通常以“三者取中法”来选取基准记录即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。
三向快速排序 实际应用中经常会出现含有大量重复元素的数组。例如一个元素全部重复的子数组就不需要继续排序了但我们的算法还会继续将它切分为更小的数组。在有大量重复元素的情况下快速排序的递归性会使元素全部重复的子数组经常出现这就有很大的改进潜力经当前实现的线性对数级的性能提高到线性级别。
算法描述 在lt之前的(lo~lt-1)都小于中间值 在gt之前的(gt1~hi)都大于中间值 在lt~i-1的都等于中间值 在i~gt的都还不确定最终i会大于gt即不确定的将不复存在
代码实现
public static void sortThreeWay(int[] a, int lo, int hi) {if (lo hi) {return;}int v a[lo], lt lo, i lo 1, gt hi;while (i gt) {if (a[i] v) {swap(a, i, lt);} else if (a[i] v) {swap(a, i, gt--);} else {i;}}sortThreeWay(a, lo, lt - 1);sortThreeWay(a, gt 1, hi);
}private static void swap(int[] a, int i, int j) {int t a[i];a[i] a[j];a[j] t;
}复杂度分析 以下是快速排序算法复杂度: 平均时间复杂度 O(nlog₂n) 最好情况 O(nlog₂n) 最坏情况 O(n²) 空间复杂度 O(1)原地分区递归版
归并排序 归并排序是建立在归并操作上的一种有效的排序算法1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法Divide and Conquer的一个非常典型的应用且各层分治递归可以同时进行。
基本思想 归并排序算法是将两个或两个以上有序表合并成一个新的有序表即把待排序序列分为若干个子序列每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
算法描述 归并排序可通过两种方式实现: 自上而下的递归 自下而上的迭代
递归法假设序列共有n个元素 将序列每相邻两个数字进行归并操作形成 floor(n/2)个序列排序后每个序列包含两个元素 将上述序列再次归并形成 floor(n/4)个序列每个序列包含四个元素 重复步骤2直到所有元素排序完毕。
迭代法 申请空间使其大小为两个已经排序序列之和该空间用来存放合并后的序列 设定两个指针最初位置分别为两个已经排序序列的起始位置 比较两个指针所指向的元素选择相对小的元素放入到合并空间并移动指针到下一位置 重复步骤3直到某一指针到达序列尾 将另一序列剩下的所有元素直接复制到合并序列尾
代码实现 归并排序其实要做两件事 分解将序列每次折半拆分 合并将划分后的序列段两两排序合并 因此归并排序实际上就是两个操作拆分合并 下面是递归的方法
public class Merge {//归并所需的辅助数组private static int[] aux;public static void sort(int[] a) {//一次性分配空间aux new int[a.length];sort(a, 0, a.length - 1);}public static void sort(int[] a, int low, int high) {if (low high) {return;}int mid (low high) / 2;//将左半边排序sort(a, low, mid);//将右半边排序sort(a, mid 1, high);merge(a, low, mid, high);}/*** 该方法先将所有元素复制到aux[]中然后在归并会a[]中。方法咋归并时(第二个for循环)* 进行了4个条件判断* - 左半边用尽(取右半边的元素)* - 右半边用尽(取左半边的元素)* - 右半边的当前元素小于左半边的当前元素(取右半边的元素)* - 右半边的当前元素大于等于左半边的当前元素(取左半边的元素)* param a* param low* param mid* param high*/public static void merge(int[] a, int low, int mid, int high) {//将a[low..mid]和a[mid1..high]归并int i low, j mid 1;for (int k low; k high; k) {aux[k] a[k];}for (int k low; k high; k) {if (i mid) {a[k] aux[j];} else if (j high) {a[k] aux[i];} else if (aux[j] aux[i]) {a[k] aux[j];} else {a[k] aux[i];}}}}复杂度分析 以下是归并排序算法复杂度: 平均时间复杂度 O(nlog₂n) 最好情况 O(nlog₂n) 最坏情况 O(nlog₂n) 空间复杂度 O(n) 从效率上看归并排序可算是排序算法中的”佼佼者”. 假设数组长度为n那么拆分数组共需logn, 又每步都是一个普通的合并子数组的过程 时间复杂度为O(n) 故其综合时间复杂度为O(nlogn)。另一方面 归并排序多次递归过程中拆分的子数组需要保存在内存空间 其空间复杂度为O(n)。
总结与思考 归并排序最吸引人的性质是它能够保证将任意长度为N的数组排序所需时间和NlogN成正比它的主要缺点则是他所需的额外空间和N成正比。
基数排序 基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机Tabulation Machine, 排序器每次只能看到一个列。它是基于元素值的每个位上的字符来排序的。 对于数字而言就是分别基于个位十位 百位或千位等等数字来排序。 基数排序Radix sort是一种非比较型整数排序算法其原理是将整数按位数切割成不同的数字然后按每个位数分别比较。由于整数也可以表达字符串比如名字或日期和特定格式的浮点数所以基数排序也不是只能使用于整数。
基本思想 它是这样实现的将所有待比较数值正整数统一为同样的数位长度数位较短的数前面补零。然后从最低位开始依次进行一次排序。这样从最低位排序一直到最高位排序完成以后数列就变成一个有序序列。
基数排序按照优先从高位或低位来排序有两种实现方案 MSDMost significant digital 从最左侧高位开始进行排序。先按k1排序分组, 同一组中记录, 关键码k1相等, 再对各组按k2排序分成子组, 之后, 对后面的关键码继续这样的排序分组, 直到按最次位关键码kd对各子组排序后. 再将各组连接起来, 便得到一个有序序列。MSD方式适用于位数多的序列。 LSD Least significant digital从最右侧低位开始进行排序。先从kd开始排序再对kd-1进行排序依次重复直到对k1排序后便得到一个有序序列。LSD方式适用于位数少的序列。
算法描述 我们以LSD为例从最低位开始具体算法描述如下 取得数组中的最大数并取得位数 arr为原始数组从最低位开始取每个位组成radix数组 对radix进行计数排序利用计数排序适用于小范围数的特点
代码实现 基数排序通过序列中各个元素的值对排序的N个元素进行若干趟的“分配”与“收集”来实现排序。 分配我们将L[i]中的元素取出首先确定其个位上的数字根据该数字分配到与之序号相同的桶中 收集当序列中所有的元素都分配到对应的桶中再按照顺序依次将桶中的元素收集形成新的一个待排序列L[]。对新形成的序列L[]重复执行分配和收集元素中的十位、百位…直到分配完该序列中的最高位则排序结束
public static void sort(int[] arr) {if (arr.length 1) return;//取得数组中的最大数并取得位数int max 0;for (int i 0; i arr.length; i) {if (max arr[i]) {max arr[i];}}int maxDigit 1;while (max / 10 0) {maxDigit;max max / 10;}//申请一个桶空间int[][] buckets new int[10][arr.length];int base 10;//从低位到高位对每一位遍历将所有元素分配到桶中for (int i 0; i maxDigit; i) {int[] bktLen new int[10]; //存储各个桶中存储元素的数量//分配将所有元素分配到桶中for (int j 0; j arr.length; j) {int whichBucket (arr[j] % base) / (base / 10);buckets[whichBucket][bktLen[whichBucket]] arr[j];bktLen[whichBucket];}//收集将不同桶里数据挨个捞出来,为下一轮高位排序做准备,由于靠近桶底的元素排名靠前,因此从桶底先捞int k 0;for (int b 0; b buckets.length; b) {for (int p 0; p bktLen[b]; p) {arr[k] buckets[b][p];}}System.out.println(Sorting: Arrays.toString(arr));base * 10;}
}复杂度分析 以下是基数排序算法复杂度其中k为最大数的位数 平均时间复杂度 O(d*(nr)) 最好情况 O(d*(nr)) 最坏情况 O(d*(nr)) 空间复杂度 O(nr) 其中d 为位数r 为基数n 为原数组个数。在基数排序中因为没有比较操作所以在复杂上最好的情况与最坏的情况在时间上是一致的均为 O(d*(n r))。
我的感悟总结 基数排序更适合用于对时间, 字符串等这些 整体权值未知的数据 进行排序。 基数排序不改变相同元素之间的相对顺序因此它是稳定的排序算法。 基数排序 vs 计数排序 vs 桶排序 这三种排序算法都利用了桶的概念但对桶的使用方法上有明显差异 基数排序根据键值的每位数字来分配桶 计数排序每个桶只存储单一键值 桶排序每个桶存储一定范围的数值
扫一扫进入我的公众号
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/84475.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!