10大排序
0. 总览
1. 冒泡
(1)一直比较交换
/**
* 冒泡排序-无优化
* 每一次两两进行比较,(大交换位置)最后一个是最大的
* 相邻之间进行比较
* [0,len) len-1 最大
* [0,len-1) len-2 最大
* [0,len-2) len-3 最大
*/
public class BubbleSort1<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
for (int end = array.length-1; end > 0; end--) {
for (int begin = 1; begin <= end; begin++) {
if (cmp(begin,begin - 1) < 0){//array[i] - array[i-1]
swap(begin,begin - 1); // 交换两个数字
}
}
}
}
}
(2)在交换过程中判断是否出现 完全有序
/**
* 冒泡排序-优化
* 当在一次比较中,出现完全有序情况,结束,(即没有交换过)
*/
public class BubbleSort2<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
for (int end = array.length-1; end > 0; end--) {
boolean sorted = true;
for (int begin = 1; begin <= end; begin++) {
if (cmp(begin,begin - 1) < 0){//array[i] - array[i-1]
swap(begin,begin - 1); // 交换两个数字
sorted = false;
}
}
if (sorted){
break;
}
}
}
}
(3)在交换中判断,最后一次交换的位置,下一次的结束点
/**
* 冒泡排序-优化2
* 如果序列尾部已经局部有序,可以记录最后1次交换的位置,减少比较次数
*/
public class BubbleSort3<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
for (int end = array.length-1; end > 0; end--) {
int sortedIndex = 1;
for (int begin = 1; begin <= end; begin++) {
if (cmp(begin,begin - 1) < 0){//array[i] - array[i-1]
swap(begin,begin - 1); // 交换两个数字
sortedIndex = begin;
}
}
end = sortedIndex;
}
}
}
2. 选择
/**
* [a,a,a,a] 从前4个里找到 最大的下标,与最后一个 (第4个) 交换
* [a,a,a,a] 从前3个里找到 最大的下标,与最后一个 (第3个) 交换
* [2,2,1]
*/
public class SelectionSort <T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
for (int end = array.length-1; end > 0 ; end--) {
int maxIndex = 0; // 初始下标
for (int begin = 1; begin <= end; begin++) {
if (cmp(maxIndex,begin) <= 0){ // arr[maxIndex]-arr[begin]
maxIndex = begin;
}
}
swap(maxIndex,end); // 最大值下标 与 最后一个下标交换位置
}
}
}
3. 插入
(1)冒泡交换
/**
* 插入排序
* 与之前的 已经排好序的 进行 冒泡交换
*/
public class InsertionSort1 <T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
for (int begin = 1; begin < array.length; begin++) {
int cur = begin;
while (cur > 0 && cmp(cur,cur-1) < 0) {
swap(cur,cur-1);
cur--;
}
}
}
}
(2)后移插入
/**
* 插入排序
* 将 交换 改为 移动
* 最后将 元素直接 放到最终位置
*/
public class InsertionSort2<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
for (int begin = 1; begin < array.length; begin++) {
int cur = begin;
T t = array[begin]; // 备份元素
while (cur > 0 && cmp(t,array[cur-1]) < 0) {
array[cur] = array[cur-1]; // 往后挪动
cur--;
}
array[cur] = t;
}
}
}
(3)二分后移插入
/**
* 插入排序-优化2
* 二分搜索进行优化
*/
public class InsertionSort3 <T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
for(int begin = 1; begin < array.length; begin++){
// 将遍历到的元素插入到前面已经排好序的序列中
insert(begin, search(begin)); //search() 查找到要插入的位置
}
}
/**
* 将source位置的元素插入到dest位置
*/
private void insert(int source, int dest){
T v = array[source]; // 备份要插入的元素
// 将 [insertIndex, begin)范围内的元素往右边挪动一个单位
for(int i = source; i > dest; i--){
array[i] = array[i - 1];
}
array[dest] = v;
}
/**
* 利用二分搜索找到index位置元素的待插入位置
* 已经排好序数组的区间范围是[0,index)
* 存在多个,找到最右边的下标,的下一个
* [begin,end)
* begin == 0 最小的
* begin == end 最大的
* begin == (0,end-1) 中间
*/
private int search(int index){
int begin = 0;
int end = index;
while(begin < end){
int mid = (begin + end) >> 1;
if(cmp(array[index], array[mid]) < 0){
end = mid;
}else if(cmp(array[index], array[mid]) == 0){ // 满足,往右找
begin = mid + 1;
}else{ // > 0
begin = mid + 1;
}
}
return begin;
}
}
4. 堆排序
是对选择的提升
/**
* 堆排序
* 1. 原地建堆
* 2. 交换堆顶元素与堆尾元素(把最大值放到最后面)
* 堆的元素数量减 1(不管最后已经放到最后的最大值)
* 对 0 位置进行 1 次 heapity 操作
*
*
* 关于堆的叶子结点,与非叶子节点,以数组为例:[0,n) ,数组长度n
* 根节点: i ; 左孩子:i*2 +1 ; 右孩子:i*2 +2 ;
* 假设 2 种情况: 最后一个非叶子结点:i,---> a. 只有左孩子 || b. 左右孩子都有
* a. 2* i + 1 = n-1 i = n/2-1 (n为偶数)
* b. 2* i + 2 = n-1 i = n/2-1.5 (n为奇数)
* b 的情况根据 java的:整数除不尽时向下取整 ,等价为 n/2 -1
* 所以最后一个非叶子节点的下标为 n/2 - 1 ;
*
*
* 自上而下的上滤时间复杂度:O(n log n)
* 自下而上的下滤时间复杂度:O(n log k) 继续计算的话会是 O(n)
*/
public class HeapSort<T extends Comparable<T>> extends Sort<T> {
private int heapSize; // 堆大小(里面有多少元素)
@Override
protected void sort() {
heapSize = array.length;
//原地建堆
buildHeap();
// 堆中只有一个元素,结束
while (heapSize > 1) {
// 交换堆顶元素和尾部元素
swap(0,--heapSize); // 下标与堆大小的关系
// 对新的堆顶:0,进行堆化
heapity(0);
}
}
// 对 0 - n/2-1 的非叶子节点进行自下向上进行堆化,构建大顶堆
// 自下而上的下滤 O(n log k)
public void buildHeap() {
for (int i = (heapSize)/2-1; i >= 0 ; i--) {
heapity(i);
}
}
// 堆化 以满足大顶堆 条件
private void heapity(int index) {
while (true) {
int maxValueIndex = index;
// 下面2个if得到 根,左,右 3节点的最大节点下标
if (2 * index +1 < heapSize && cmp(maxValueIndex,2*index+1) < 0){
maxValueIndex = 2 * index + 1; // 左节点比父节点大
}
if (2 * index +2 < heapSize && cmp(maxValueIndex,2*index+2) < 0){
maxValueIndex = 2 * index + 2; // 右节点比父节点,或左节点大
}
if (maxValueIndex == index){
break; // 说明当前节点值为最大值,无需往下迭代
}
swap(index,maxValueIndex);
index = maxValueIndex;
}
}
}
5. 归并
/**
* 归并排序
*/
@SuppressWarnings("unchecked")
public class MergeSort <T extends Comparable<T>> extends Sort<T> {
private T[] leftArray;
@Override
protected void sort() {
// 准备一段临时的数组空间, 在merge操作中使用
leftArray = (T[])new Comparable[array.length >> 1];
sort(0, array.length);
}
/**
* 对 [begin, end) 范围的数据进行归并排序
*/
private void sort(int begin, int end){
if(end - begin < 2) {
return; // 至少要2个元素
}
int mid = (begin + end) >> 1;
sort(begin, mid); // 归并排序左半子序列
sort(mid, end); // 归并排序右半子序列
merge(begin, mid, end); // 合并整个序列
}
/**
* 将 [begin, mid) 和 [mid, end) 范围的序列合并成一个有序序列
* 两个数组 : leftArray ,[begin,end) :array的一个范围
* 最终 [begin,end) 有序;
*/
private void merge(int begin, int mid, int end){
int li = 0, le = mid - begin; // 左边数组(基于leftArray)
int ri = mid, re = end; // 右边数组(array)
int ai = begin; // array的索引,范围是[begin,end);
// 备份左边数组到leftArray
for(int i = li; i < le; i++){
leftArray[i] = array[begin + i];
}
/**
* cmp(array[ri], leftArray[li]) : array[ri] - leftArray[li]
* 稳定性(相同数字不改变相对位置):[L,l] 排完序 : [L,l] (稳定) ; [l,L] (不稳定)
*
* 左边结束(li < le)----->直接结束 (右边本来就在应在位置上)
* 右边结束(ri < re)----->直接赋值 (leftArray赋值)
*/
// 如果左边还没有结束
while(li < le){ // li == le 左边结束, 则直接结束归并
// 右边合法 && 右边小
if(ri < re && cmp(array[ri], leftArray[li]) < 0){ // cmp改为<=0会失去稳定性
array[ai++] = array[ri++]; // 右边<左边, 拷贝右边数组到array
}else{
array[ai++] = leftArray[li++]; // 左边<=右边, 拷贝左边数组到array
}
}
}
}
6. 快速
(1)轴点排序法1,整体上是优秀的
开始表格里的复杂度按照第一个排序说明的
/**
* 快速排序
* 轴元素
* 左右指针移动
*/
public class QuickSort<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
sort(0, array.length);
}
/**
* 对 [begin, end) 范围的元素进行快速排序
*/
private void sort(int begin, int end){
if (end - begin < 2){ // 只有一个元素
return;
}
// 确定轴点元素
int mid = pivotIndex(begin, end);
// 对子序列进行快速排序
sort(begin,mid);
sort(mid+1,end);
}
/**
* 构造出 [begin, end) 范围的轴点元素
* @return 轴点元素的最终位置
*/
private int pivotIndex(int begin, int end){
// 不能将begin作为轴点,防止最坏情况(恶意): 7,1,2,3,4,5,6
// 随机位置与轴点 交换顺序
swap(begin,begin + (int)Math.random()*(end-begin));
// 备份begin 位置的元素
T pivot = array[begin];
// end 指向最后一个元素
end--;
while (begin < end) {
// 1. 两个while 将左右指针的转移,完美
while (begin < end) {// 从右往左扫描
//右指针开始
// 轴点相等的元素,要放到相反位置,不可以 <= , >=
// 结果是 子序列极度不均匀,出现最坏复杂度O (n^2)
// 个人感觉--> 左右指针转化次数越多,越不可能出现最坏复杂度
if (cmp(pivot,array[end]) < 0){ // 轴 < 右边 ,
end--;
}else { // 轴 >= 右边,放到另一边
array[begin++] = array[end];
break;
}
}
// 2.
while (begin < end) {// 从左往右扫描
if (cmp(pivot,array[begin]) > 0){// 轴 > 左边 ,
begin++;
}else {
array[end--] = array[begin];
break;
}
}
}
// 放入最终位置
array[begin] = pivot;
// 返回轴点元素的位置
return begin;
}
}
(2) 对于轴点的另一种排序:整体是很不好的,好记忆
private int pivotIndex(int begin, int end){
// 不能将end作为轴点,防止最坏情况(恶意): 7,1,2,3,4,5,6
// 随机位置与轴点 交换顺序
swap(begin,begin + (int)(Math.random()*(end-begin)));
// index 保证在它前面的都是 比 begin 轴点小的数字
int index = begin+1;
for (int i = begin + 1; i < end; i++) {
if (cmp(array[i],array[begin]) < 0){
swap(i,index);
index++;
}
}
swap(begin,index-1);
// 返回轴点元素的位置
return index-1;
}
7. 希尔
/**
* 希尔排序
*
* 希尔排序把序列看作是一个矩阵,分成 𝑚 列,逐列进行排序
*
* 𝑚 从某个整数逐渐减为1
* 当 𝑚 为1时,整个序列将完全有序
* 因此,希尔排序也被称为递减增量排序(Diminishing Increment Sort)
*
* 矩阵的列数取决于步长序列(step sequence):
*
* [0,1,2,4]
* [5,6,7,8]
*/
内部是插入排序
public class ShellSort <T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
// 根据元素数量算出步长序列
List<Integer> stepSequence = sedgewickStepSequence();
// 按步长序列划分进行排序
for (Integer step : stepSequence) {
sort(step); // 按step进行排序
}
}
/**
* 分成step列进行排序
*/
private void sort(int step){
// 列 column
for (int col = 0; col < step; col++) {
// 内部是插入排序,可用最优的二分插入排序,这里用最简单的,明白原理是关键
for (int begin = col + step; begin < array.length; begin += step) {
// col+step , col + 2*step , col + 3*step
int cur = begin;
// cur > col 或 cur > step-1
while (cur > col && cmp(cur,cur - step) < 0) {
swap(cur,cur - step);
cur -= step;
}
}
}
}
/**
* 希尔本人提出的步长序列
* 1,2,4,8,16,32
*/
public List<Integer> shellStpSequence(){
LinkedList<Integer> lists = new LinkedList<>();
int len = array.length;
// 每次取一半
while ((len >>= 1) > 0){
lists.add(len);
}
return lists;
}
/**
* 目前效率最高的步长序列
* 最坏情况时间复杂度是 O(n4/3) ,1986年由 Robert Sedgewick 提出
*/
private List<Integer> sedgewickStepSequence() {
List<Integer> stepSequence = new LinkedList<>();
int k = 0, step = 0;
while (true) {
if (k % 2 == 0) {
int pow = (int) Math.pow(2, k >> 1);
step = 1 + 9 * (pow * pow - pow);
} else {
int pow1 = (int) Math.pow(2, (k - 1) >> 1);
int pow2 = (int) Math.pow(2, (k + 1) >> 1);
step = 1 + 8 * pow1 * pow2 - 6 * pow2;
}
if (step >= array.length) {
break;
}
stepSequence.add(0, step);
k++;
}
return stepSequence;
}
}
8. 计数排序
(1)非负整数
/**
* 只能对 非 负数 整数 进行排序
* 及其 浪费内存空间
* 不稳定
* O(n)
*/
private void test1(){
// 数组里的最大值
int maxLen = array[0];
int len = array.length;
for (int i = 1; i < len; i++) {
if (array[i] > maxLen){
maxLen = array[i];
}
}
// 存放数字出现次数 的数组
int[] counts = new int[maxLen+1];
for (int i = 0; i < len; i++) {
counts[array[i]]++;
}
// 输出
int index = 0;
for (int i = 0; i < maxLen+1; i++) {
while (counts[i]-- > 0) {
array[index++] = i;
}
}
}
(2)整数
/**
* 最终版
* 所有整数
* 取范围
* 可以稳定
*/
private void test2(){
// 数组里的最大值,最小值 确定长度
int maxLen = array[0];
int minLen = array[0];
for (int i = 1; i < array.length; i++) { // 不可以if-else min,max可能相同
if (array[i] > maxLen){
maxLen = array[i];
}
if (array[i] < minLen){
minLen = array[i];
}
}
// // 开辟内存空间,存储次数
int[] counts = new int[maxLen - minLen +1];
for (int i = 0; i < array.length; i++) {
counts[array[i] - minLen]++; // 数字 与 下标 相对应
}
// 累加次数
for (int i = 1; i < counts.length; i++) {
counts[i] += counts[i-1];
}
// 从后往前遍历元素,将它放到有序数组中的合适位置
int[] newArray = new int[array.length];
for (int i = array.length-1; i >= 0 ; i--) {
newArray[--counts[array[i] - minLen]] = array[i];
}
// 将有序数组赋值到array
for (int i = 0; i < array.length; i++) {
array[i] = newArray[i];
}
}
(3)对象也可以用:
/**
* 对 对象的 整数 属性 进行排序
*/
public static void main(String[] args) {
Person[] array = new Person[] {
new Person(20, "A"),
new Person(-13, "B"),
new Person(17, "C"),
new Person(12, "D"),
new Person(-13, "E"),
new Person(20, "F")
};
// 数组里的最大值,最小值 确定长度
int maxLen = array[0].age;
int minLen = array[0].age;
for (int i = 1; i < array.length; i++) { // 不可以if-else min,max可能相同
if (array[i].age > maxLen){
maxLen = array[i].age;
}
if (array[i].age < minLen){
minLen = array[i].age;
}
}
// // 开辟内存空间,存储次数
int[] counts = new int[maxLen - minLen +1];
for (int i = 0; i < array.length; i++) {
counts[array[i].age - minLen]++; // 数字 与 下标 相对应
}
// 累加次数
for (int i = 1; i < counts.length; i++) {
counts[i] += counts[i-1];
}
// 从后往前遍历元素,将它放到有序数组中的合适位置
Person[] newArray = new Person[array.length];
for (int i = array.length-1; i >= 0 ; i--) {
newArray[--counts[array[i].age - minLen]] = array[i];
}
// 将有序数组赋值到array
for (int i = 0; i < array.length; i++) {
array[i] = newArray[i];
}
// 打印
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
/**
* 自定义对象
*/
private static class Person {
int age;
String name;
Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person [age=" + age
+ ", name=" + name + "]";
}
}
9. 基数排序
/**
* 基数排序
* 基数排序非常适合用于整数排序(尤其是非负整数),这里只介绍对非负整数进行基数排序。
* https://blog.csdn.net/weixin_43734095/article/details/105170908
* 个位 ,十位, 百位。。。。
*/
public class RadixSort extends Sort<Integer> {
@Override
protected void sort() {
int max = array[0];
for (int i = 0; i < array.length; i++) {
if (max < array[i]){
max = array[i];
}
}
// 个位数: array[i] / 1 % 10 = 3
// 十位数:array[i] / 10 % 10 = 9
// 百位数:array[i] / 100 % 10 = 5
// 千位数:array[i] / 1000 % 10 = ...
// 传值 : 1,10,100,1000.....
for (int divider = 1; divider <= max; divider *= 10) {
test2(divider);
}
}
// 计数排序
private void test2(int divider){
// 开辟内存空间,存储次数 ,只会是 0-9
int[] counts = new int[10];
for (int i = 0; i < array.length; i++) {
counts[array[i] / divider % 10]++; // 数字 与 下标 相对应
}
// 累加次数
for (int i = 1; i < counts.length; i++) {
counts[i] += counts[i-1];
}
// 从后往前遍历元素,将它放到有序数组中的合适位置
int[] newArray = new int[array.length];
for (int i = array.length-1; i >= 0 ; i--) {
newArray[--counts[array[i] / divider % 10]] = array[i];
}
// 将有序数组赋值到array
for (int i = 0; i < array.length; i++) {
array[i] = newArray[i];
}
Integers.println(array);
}
}
10. 桶排序
…