常见排序算法插排,归并,快速,堆排序-java

本文深入讲解了各种排序算法,包括插入排序、希尔排序、堆排序、归并排序、快速排序等,详细分析了每种算法的时间复杂度和空间复杂度,并提供了具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

常见排序算法

排序

时间复杂

类别平均复杂度最好最坏空间复杂度
插入排序n^2nn^21
希尔排序n^1.3nn^21
堆排序nlognnlognnlogn1
归并排序nlognnlognnlognn
插入排序nlognnlognn^2n or logn
基数排序ndndndr + n

插入排序

运行时间N^2
将每个位置元素插入到合适的位置,其他位置后移

/**
 * 将数组指定范围内进行插入排序
 */
public static <E extends Comparable<? super E>> void insertionSort(E[] a, int left, int right) {
    int j;
    // 插排执行的交换次数是逆序的个数(具有性质i<j但a[i]>a[j]的序偶(a[i],a[j]))
    // 相当于把每个值向前遍历插入到正确的位置
    for (int i = left + 1; i <= right; i++) {
        E temp = a[i];
        for (j = i; j > left && temp.compareTo(a[j - 1]) < 0; j--) {
            // 将大于i的值整体右移
            a[j] = a[j - 1];
        }
        // 此时的j为temp的正确位置
        a[j] = temp;
    }
}

希尔排序

最坏运行时间N2,有时能达到N(3/2)
log N次小数组插入排序,每个数组间隔gap,又称增量序列,即缩减增量排序

/**
 * 运行时间最差N^2 ,使用1,3,7,...,2^k - 1增量序列运行时间优化至N^(3/2)
 * 希尔排序:实际上是n个增量序列分别进行插入排序,且第一个序列h1必须为1
 */
public static <E extends Comparable<? super E>> void shellSort(E []a) {
    System.out.println(Math.log(8));
    int j;
    // 第一个for循环,增量循环 次数为log N
    for (int gap = a.length / 2; gap > 0; gap /= 2) {
        // 单次插排,元素为间隔gap增量的序列
        for (int i = gap; i < a.length; i++) {
            E temp = a[i];
            for (j = i; j >= gap && temp.compareTo(a[j - gap]) < 0; j -= gap) {
                a[j] = a[j - gap];
            }
            a[j] = temp;
        }
    }
}

堆排序

运行时间Nlog N
利用二叉堆的堆序性质进行排序

利用一个数组完成排序,根为最大值,每次排序交换根与二叉堆最后一个元素+1位置的元素,然后对根进行下滤

public static final int DESC = 1;
public static final int ASC = 0;

/**
 * 堆数组从0开始  故而左子节点为当前节点的索引*2+1
 * @param i
 * @return
 */
private static int leftChild(int i) {
    return 2 * i + 1;
}

/**
 * 下滤,构成根为最小值堆序的二叉堆;用于构建降序数组
 * @param a  堆的源数组
 * @param i 下滤的索引位置
 * @param n 二叉堆的长度
 */
private static <E extends Comparable<? super E>> void percUp(E [] a, int i, int n) {
    int child;
    E temp = a[i];
    while (leftChild(i) < n) {
        child = leftChild(i);
        if (child != n - 1 && a[child].compareTo(a[child + 1]) > 0) {
            child++;
        }
        if (temp.compareTo(a[child]) > 0) {
            a[i] = a[child];
        } else {
            break;
        }
        i = child;
    }
    a[i] = temp;
}

/**
 * 下滤,构成根为最大值堆序的二叉堆;用于构建升序数组
 * @param a 二叉堆性质的数组
 * @param i 需要下滤的索引位置
 * @param n 二叉堆的长度
 */
private static <E extends Comparable<? super E>> void percDown(E [] a, int i, int n) {
    int child;
    E temp;
    for (temp = a[i]; leftChild(i) < n; i = child) {
        child = leftChild(i);
        // 左子节点小于右子节点,则空穴hop下滤到右子节点
        if (child != n - 1 && a[child].compareTo(a[child + 1]) < 0) {
            child++;
        }
        // 空穴下滤,大的值上移
        if (temp.compareTo(a[child]) < 0) {
            a[i] = a[child];
        } else {
            break;
        }
    }
    a[i] = temp;
}

public static <E extends Comparable<? super E>> void heapSort(E [] a, int sort) {
    boolean isDesc = DESC == sort;

    // 最下层的数据不需要操作
    for (int i = a.length / 2 - 1; i >= 0; i--) {
        if (isDesc) {
            percUp(a, i, a.length);
        } else {
            percDown(a, i, a.length);
        }
    }
    // deleteMax
    for (int i = a.length - 1; i > 0; i--) {
        swapReferences(a, 0, i);
        if (isDesc) {
            percUp(a, 0, i);
        } else {
            percDown(a, 0, i);
        }
    }
    for (E e : a) {
        System.out.println(e);
    }
}

private static <E extends Comparable<? super E>> void swapReferences(E [] a, int i, int n) {
    E temp = a[n];
    a[n] = a[i];
    a[i] = temp;
}

归并排序

运行时间Nlog N
分治迭代 ,比较次数少
基本操作是合并两个已排序的表

public static <E extends Comparable<? super E>> void mergeSort(E[] a) {
    E [] tempArray = (E[]) new Comparable[a.length];
    mergeSort(a, tempArray, 0, a.length - 1);
    for (E e : a) {
        System.out.println(e);
    }
}

private static <E extends Comparable<? super E>> void mergeSort(E[] a, E[] tempArray, int left, int right) {
    if (left < right) {
        int center = (left + right) / 2;
        mergeSort(a, tempArray, left, center);
        mergeSort(a, tempArray, center + 1, right);
        merge(a, tempArray, left, center + 1, right);
    }
}

/**
 * 进行合并操作 默认a,tempArray有序数组
 * @param a 需要进行排序的数组
 * @param tempArray  临时存储空间
 * @param leftPos  第一个有序数组的最左索引
 * @param rightPos  第二个有序数组的最左索引
 * @param rightEnd  第二个有序数组的最右索引
 */
private static <E extends Comparable<? super E>> void merge(E[] a, E[] tempArray, int leftPos, int rightPos, int rightEnd) {
    int leftEnd = rightPos - 1;
    int tmpPos = leftPos;
    int numElements = rightEnd - leftPos + 1;

    // Main loop
    while (leftPos <= leftEnd && rightPos <= rightEnd) {
        if (a[leftPos].compareTo(a[rightPos]) <= 0) {
            tempArray[tmpPos++] = a[leftPos++];
        } else {
            tempArray[tmpPos++] = a[rightPos++];
        }
    }

    while (leftPos <= leftEnd) {
        tempArray[tmpPos++] = a[leftPos++];
    }

    while (rightPos <= rightEnd) {
        tempArray[tmpPos++] = a[rightPos++];
    }

    // 复制tempArray到a
    System.arraycopy(tempArray, rightEnd - numElements + 1, a, rightEnd - numElements + 1, numElements);
    // 或者以下for循环复制
//        for (int i = 0; i < numElements; rightEnd--, i++) {
//            a[rightEnd] = tempArray[rightEnd];
//        }
}

快速排序

运行时间NlogN
取枢纽元pivot
一种分治的递归算法

枢纽元左边都是小于枢纽元的元素,右边都是大于枢纽元的元素:本质上是把枢纽元放到合适的位置上

取枢纽元:三数中值分割法

private static <E extends Comparable<? super E>> E median3(E[] a, int left, int right) {
    int center = (left + right) / 2;
    // 保证最左,最右小于中位值
    if (a[left].compareTo(a[center]) > 0) {
        swapReferences(a, left, center);
    }
    if (a[right].compareTo(a[left]) < 0) {
        swapReferences(a, left, right);
    }
    if (a[right].compareTo(a[center]) < 0) {
        swapReferences(a, center, right);
    }
    // 中位数与最右边倒数第二个元素互换位置
    swapReferences(a, center, right - 1);
    return a[right - 1];
}

快速排序:当需要排序的元素小于10时使用插入排序

public static <E extends Comparable<? super E>> void quickSort(E[] a) {
    quickSort(a, 0, a.length - 1);
}

private static final int CUTOFF = 4;
private static <E extends Comparable<? super E>> void quickSort(E[] a, int left, int right) {
    if (left + CUTOFF <= right) {
        E pivot = median3(a, left, right);

        // begin partitioning
        int i = left, j = right - 1;
        while (true) {
            while (a[++i].compareTo(pivot) < 0) {}
            while (a[--j].compareTo(pivot) > 0) {}
            if (i < j) {
                swapReferences(a, i, j);
            } else {
                break;
            }
        }
        // pivot back
        swapReferences(a, i, right - 1);

        quickSort(a, left, i - 1);
        quickSort(a, i + 1, right);
    } else {
        InsertSorted.insertionSort(a, left, right);
    }
}

/**
 * 互换位置
 */
private static <E extends Comparable<? super E>> void swapReferences(E[] a, int left, int right) {
    E temp = a[left];
    a[left] = a[right];
    a[right] = temp;
}
快速选择

针对选择问题,即第k个最大元或最小元
运行时间最坏N^2,平均N

第k个最小元:

public static <E extends Comparable<? super E>> void quickSelect(E [] a, int left, int right, int k) {
    if (left + CUTOFF <= right) {
        // 前三步快速排序
        E pivot = median3(a, left, right);

        int i = left, j = right - 1;

        for (;;) {
            while (a[++i].compareTo(pivot) < 0) {}
            while (a[--j].compareTo(pivot) > 0) {}
            if (i < j) {
                swapReferences(a, i, j);
            } else {
                break;
            }
        }

        swapReferences(a, i, right - 1);
        // 选择问题
        if (k <= i) {
            quickSelect(a, left, i - 1, k);
        } else if (k > i + 1) {
            quickSelect(a, i + 1, right, k);
        }
    } else {
        InsertSorted.insertionSort(a, left, right);
    }


}

线性时间的排序:桶排序和基数排序

使用桶排序需要一些附加的信息,如输入数据必须仅有小于M的正整数组成

即使用一个大小为M的变量名count的数组,初始全为0.此时count有M个单元(桶),初始计数为空。输入N个数据,当读入一个输入a时,count[a] += 1,增1,此时索引顺序就是排序顺序
运行时间O(M+N)

桶排序

public static void bucketSort(int[] intArr) {
    // 输入不超过100的排序
    int[] bucketArr = new int[100];
    for (int i = 0; i < intArr.length; i++) {
        bucketArr[intArr[i]] += 1;
    }
    int flag = 0;
    for (int i = 0; i < bucketArr.length; i++) {
        if (bucketArr[i] != 0) {
            for (int j = 0; j < bucketArr[i]; j++) {
                intArr[flag++] = i;
            }
        }
    }
}

基数排序
字符串的基数排序

/**
 * 计数基数排序,本质是多躺桶排序
 * @param arr
 * @param stringLen
 */
public static void radixSortA(String[] arr, int stringLen)
{
    final int BUCKETS = 256;
    // 泛型数组不建议使用
    ArrayList<String> [] buckets = new ArrayList[BUCKETS];

    // 初始化桶
    for (int i = 0; i < BUCKETS; i++) {
        buckets[i] = new ArrayList<>();
    }

    for (int pos = stringLen - 1; pos >= 0; pos--) {
        for (String s : arr) {
            buckets[s.charAt(pos)].add(s);
        }

        int idx = 0;
        for (ArrayList<String> theBucket : buckets) {
            for (String s : theBucket) {
                arr[idx++] = s;
            }
            theBucket.clear();
        }
    }
}

定长字符串的计数基数排序

/**
 * 定长字符串的计数基数排序
 * @param arr
 * @param stringLen
 */
public static void countingRadixSort(String[] arr, int stringLen) {
    final  int BUCKETS = 256;

    int n = arr.length;
    String[] buffer = new String[n];

    String[] in = arr;
    String[] out = buffer;

    for (int pos = stringLen - 1; pos >= 0; pos--) {
        int[] count = new int[BUCKETS + 1];

        for (int i = 0; i < n; i++) {
            count[in[i].charAt(pos) + 1]++;
        }

        for (int i = 1; i <= BUCKETS; i++) {
            count[i] += count[i - 1];
        }

        for (int i = 0; i < n; i++) {
            out[count[in[i].charAt(pos)]++] = in[i];
        }
        String[] temp = in;
        in = out;
        out = temp;

    }

    if (stringLen % 2 == 1) {
        for (int i = 0; i < arr.length; i++) {
            out[i] = in[i];
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值