作为一名刚上大三的学生,还是希望能目光长远一点,将目标定于明年的春招,所谓,宝剑锋从磨砺出,梅花香自苦寒来,相信很多同学跟我一样,都已经开始准备明年的春招了,所以,博主希望跟大家一起努力,收获成功!

文章目录

冒泡排序

十大排序算法总结_排序算法

public static int[] bubbleSort(int[] array){

int length = array.length;
for(int i = 0;i < length - 1;i++){
for(int j = i + 1;j < length - i - 1;j++){
if(array[j] > array[j+1]){
int temp = array[j];
array[j] = array[j+1];
array[j + 1] = temp;
}
}
}
return array;
}
  • 在乱序的条件下的时间复杂度是O(n^2),有序情况下的时间复杂度是O(n)
  • 稳定排序
  • 空间复杂度:O(1)

选择排序

算法思路:

首先找到数组中的最小元素,然后将这个最小元素和数组的第一个元素交换位置,如果第一个元素就是最小元素,就和自己交换位置;再次,在剩下的元素中找到最小元素和数组中的第二个元素交换位置,如此往复,直到将整个数组排序,一句话总结就是,不断在剩余元素中找最小元素

/**
* 选择排序
*
* @param array
* @return
*/
public static int[] selectSort(int[] array) {
int length = array.length;
System.out.println("length:" + length);
//找出最小下标
//外层循环指向当前元素
for (int i = 0; i < length - 1; i++) {
//最小下标
int minIndex = i;
//内层循环负责找到最小元素
for (int j = i + 1; j < length; j++) {
//跟最小下标对应数比较
if (array[j] < array[minIndex]) {
minIndex = j;
}
}
//将找到的最小值和i位置所在的值进行交换
if (i != minIndex) {
int tmp = array[i];
array[i] = array[minIndex];
array[minIndex] = tmp;
}
}
return array;
}
  • 不稳定排序
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 选择排序是数据移动最少的,每次交换两会改变两个数组元素的值,因此选择排序用了N次交换,交换次数和数组大小是线性关系

快速排序

算法思路

快速排序是将一个数组分成两个子数组,将两个子数组分别独立排序,当两个子数组有序时数组就有序,需要找到一个基准值来划分数组成两个子数组,分别是比基准值大和比基准值小,递归的进行排序

十大排序算法总结_数组_02

public static void quickSort(int[] nums, int left, int right) {
if (left < right) {
int low = left;
int hight = right;
int key = nums[left];
while (low < hight) {
//从高位向低位
while (low < hight && nums[hight] >= key) {
hight--;
}
if (low < hight) {
nums[low++] = nums[hight];
}
//低位向高位
while (low < hight && nums[low] < key) {
low++;
}
if (low < hight) {
nums[hight--] = nums[low];
}
}
nums[low] = key;
quickSort(nums, left, low - 1);
quickSort(nums, low + 1, right);
}
}

python实现

def quickSort(array):
if len(array) < 2:
return array
else
pivot = array[0]
less = [i for i in array[1:] if i <= pivot]
greater = [i for i in array[1:] if i > pivot]
return quickSort(less) + [pivot] + quickSort(greater)
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
  • 不稳定排序
  • 算法思想:分治

快速排序优化:

  • 对于小数组,快速排序比插入排序要慢,所以当对小数组进行排序时,可以切换到插入排序

归并排序

算法思路:

归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序

十大排序算法总结_数组_03

十大排序算法总结_算法_04

十大排序算法总结_数据结构和算法_05

/**
* 归并排序
* 时间复杂度:O(n*logn)
* @param oldArray
* @param tmpArray
* @param left
* @param right
*/
public static void mergeSort(int[] oldArray,int[] tmpArray,int left,int right){

if(left < right){
int center = (left + right) / 2;
//分治排序左子数组
mergeSort(oldArray,tmpArray,left,center);
//分治排序右子数组
mergeSort(oldArray,tmpArray,center+1,right);
//分开排序后合并成一个新数组在copy回原数组,此时原数组已经有序
merge(oldArray, tmpArray, left, center+1,right);
}
}

private static void merge(int[] oldArray, int[] tmpArray, int leftStart, int rightStart, int rightEnd) {

//左数组结束下标
int leftEnd = rightStart - 1;
//辅助数组起始下标
int tmpPos = leftStart;
//数组元素数量
int numElements = rightEnd - tmpPos + 1;
//左右数组进行比较,按序放入辅助数组
while(leftStart <= leftEnd && rightStart <= rightEnd){
if(oldArray[leftStart] <= oldArray[rightStart]){
tmpArray[tmpPos++] = oldArray[leftStart++];
}else{
tmpArray[tmpPos++] = oldArray[rightStart++];
}
}
//检查是否还有剩下元素
while(leftStart <= leftEnd){
tmpArray[tmpPos++] = oldArray[leftStart++];
}
while(rightStart <= rightEnd){
tmpArray[tmpPos++] = oldArray[rightStart++];
}
//复制回原来旧数组
for(int i = 0;i<numElements;i++,rightEnd--) {
oldArray[rightEnd] = tmpArray[rightEnd];
}
}
  • 算法思想:分治
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
  • 稳定排序

堆排序

堆排序是基于二叉堆(优先队列)实现的,通过建立最大堆或者最小堆,删除堆顶的最值元素后将堆顶元素放入指定集合,重新建堆,最后集合便是有序序列

十大排序算法总结_数据结构和算法_06

/**
* 堆排序:【最小堆】 --- 降序
* - 建堆
* - 删除堆顶
* - 循环以上两步
*
* @param arr
*/
public void heapSortOfMinHeap(int[] arr) {
int len = arr.length;
buildMinHeap(arr, len);
while (len > 1) {
swap(arr, 0, len - 1);
len--;
heapifyForMinHeap(arr, 0, len);
}
}


/**
* 构建最小堆
*
* @param arr
* @param len
*/
private void buildMinHeap(int[] arr, int len) {
for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
heapifyForMinHeap(arr, i, len);
}
}


/**
* 构建最小堆
*
* @param arr
* @param i
* @param len
*/
private void heapifyForMinHeap(int[] arr, int i, int len) {
//左子节点
int left = i * 2 + 1;
//右子节点
int right = i * 2 + 2;
//辅助变量 -- 根节点
int minest = i;
//节点比较,小节点上浮
if (left < len && arr[left] < arr[minest]) {
minest = left;
}
if (right < len && arr[right] < arr[minest]) {
minest = right;
}
//完成交换
if (minest != i) {
swap(arr, i, minest);
heapifyForMinHeap(arr, minest, len);
}
}


/**
* 交换函数
*
* @param array
* @param i
* @param j
*/
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
  • 不稳定排序
  • 时间复杂度:O(nlogn)

插入排序

算法思路:

与选择排序一样,当前索引左边的所有元素都是有序的,但是他们的最终位置还不确定,为了给更小的元素腾出空间,他们可能会被移动,但是当索引到达数组的末端,数组排序就完成了

十大排序算法总结_java_07

/**
* 插入排序
* @param array
*/
public static void insertSort(int[] array){
int j;
for(int p = 1;p < array.length;p++){
int temp = array[p];
//将array[p]插入到array[p-1],array[p-2]....
for(j = p;j > 0 && temp < array[j-1];j--){
array[j] = array[j - 1];
}
//完成插入
array[j] = temp;
}
}
  • 稳定排序
  • 时间复杂度分析:O(N^2),如果序列在排序前已经是有序序列,则为O(N)
  • 空间复杂度分析:O(1)
  • 数据量较少时效率高。插入排序适合数据量少的情况
  • 算法的实际运行效率优于选择排序和冒泡排序。
  • 插入排序对于部分有序的数组很有效,部分有序的数组类如数组中每个元素距离他的最终位置不远,一个有序的大数组接一个小数组,数组中只有几个元素的位置不正确

希尔排序

算法思想:

希尔排序是一种基于插入排序的快速排序算法,对于大规模的数据,插入排序很慢,因为随着数据规模的增大,移动规模也可能随着增大,因为插入排序只会交换相邻的元素,元素只能一点点的从数组一端移动到指定位置,但是希尔排序是使用一种步长的思想,根据指定步长分为h个子数组,每个子数组进行插入排序,形成了h个有序子数组

十大排序算法总结_算法_08

/**
* 希尔排序
* 不稳定排序
* @param array
*/
public static void hashSort(int[] array){
int j;
//外层循环控制步长
for(int grap = array.length / 2;grap > 0;grap/=2){
//内层循环其实是插入排序
for(int p = grap;p < array.length;p++){
int temp = array[p];
for(j = p;j >= grap && temp < array[j - grap];j-=grap){
array[j] = array[j - grap];
}
array[j] = temp;
}
}
}
  • 不稳定排序
  • 希尔排序的时间复杂度较直接插入排序低,它的时间是所取“增量”(步长gap)序列的函数。
    最好时间复杂度: O(n) – 有序情况下
    平均时间复杂度: O(1.2^n ~ 1.5^n) – Hibbard
    最坏时间复杂度: O(n^2) — 希尔增量
  • 空间复杂度:O(1)

桶排序

将序列中的元素分配到各自的桶。
对每个桶内的元素进行排序。可以选择任意一种排序算法。
将各个桶中的元素合并成一个大的有序序列。

十大排序算法总结_java_09

/**
* 桶排序
* @param data
* @return
*/
public static List<ArrayList<Integer>> bucketSort(int[] data){
int max = getMax(data);
int min = getMin(data);
//桶的数量取到最大,可以取max - min / array.length + 1
//int bucketNum = max - min + 1;
int bucketNum = max - min / data.length + 1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
//初始化桶
for(int i = 0;i < bucketNum;i++){
bucketArr.add(new ArrayList<>());
}
//元素放入桶中
for(int i = 0;i < data.length;i++){
int tmp = data[i] - min / data.length;
bucketArr.get(tmp).add(data[i]);
}
//每个桶分别排序
for(int i = 0;i < bucketNum;i++){
Collections.sort(bucketArr.get(i));
}
return bucketArr;
}

/**
* 获取待排序数组中的最大值
* @param array
* @return
*/
private static int getMax(int[] array){

int max = array[0];
for(int i = 1;i<array.length;i++) {
if (array[i] > max) {
max = array[i];
}
}
return max;
}

/**
* 获取待排序数组的最小值
* @param array
* @return
*/
private static int getMin(int[] array){

int min = array[0];
for(int i = 1;i<array.length;i++) {
if (array[i] < min) {
min = array[i];
}
}
return min;
}

public static void main(String[] args) {
int[] array = {1,5,2,5,8,3};
List<ArrayList<Integer>> list = bucketSort(array);
ArrayList<Integer> arrayList = new ArrayList<>();
for(ArrayList list1 : list){
arrayList.addAll(list1);
}
for(int i : arrayList){
System.out.println(i);
}
}
  • 桶排序时间复杂度可以认为接近O(n)
  • 空间复杂度取决于桶的数量
  • 桶排序是稳定排序,但是当桶内的排序是其他不稳定的排序,例如快速排序时就不再稳定了

计数排序

十大排序算法总结_java_10

/**
* 计数排序
* 稳定排序
* @param array
*/
public static void countSort(int[] array){
//拿出最大值和最小值
int max = getMax(array);
int min = getMin(array);
//建立辅助数组
int[] help = new int[max - min + 1];
//array数组中每个数和最小值的差都是一定的,根据差找到对应的数组下标,开始计数
for(int i = 0;i < array.length;i++){
int j = array[i] - min;
help[j]++;
}
int k = 0;
//恢复数组
for(int i = 0;i < help.length;i++){
while(help[i] != 0){
array[k] = i + min;
k++;
help[i]--;
}
}
}

private static int getMin(int[] array) {
int min = array[0];
for(int i = 0;i < array.length;i++){
if(min > array[i]){
min = array[i];
}
}
return min;
}

private static int getMax(int[] array){
int max = array[0];
for(int i = 0;i < array.length;i++){
if(max < array[i]){
max = array[i];
}
}
return max;
}
  • 稳定排序
  • 其空间复杂度和时间复杂度均为O(n+k)线性时间复杂度,其中k是整数的范围(取决于辅助数组大小)
  • 非比较排序
  • 计数排序其实是桶数取 max - min + 1最大时的桶排序

基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。他是根据键值的每位数字来分配桶;然后每一位每一位的比较

  1. 最低位优先法,简称LSD法:先从最低位开始排序,再对次低位排序,直到对最高位排序后得到一个有序序列;
  2. 最高位优先法,简称MSD法:先从最高位开始排序,再逐个对各分组按次高位进行子排序,循环直到最低位。

十大排序算法总结_算法_11

public static int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

int maxDigit = getMaxDigit(arr);
return radixSort(arr, maxDigit);
}

/**
* 获取最高位数
*/
private static int getMaxDigit(int[] arr) {
int maxValue = getMaxValue(arr);
return getNumLenght(maxValue);
}

private static int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}

protected static int getNumLenght(long num) {
if (num == 0) {
return 1;
}
int lenght = 0;
for (long temp = num; temp != 0; temp /= 10) {
lenght++;
}
return lenght;
}

private static int[] radixSort(int[] arr, int maxDigit) {
int mod = 10;
int dev = 1;

for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
// 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
int[][] counter = new int[mod * 2][0];

for (int j = 0; j < arr.length; j++) {
int bucket = ((arr[j] % mod) / dev) + mod;
counter[bucket] = arrayAppend(counter[bucket], arr[j]);
}

int pos = 0;
for (int[] bucket : counter) {
for (int value : bucket) {
arr[pos++] = value;
}
}
}

return arr;
}

/**
* 自动扩容,并保存数据
*
* @param arr
* @param value
*/
private static int[] arrayAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}

public static void main(String[] args) throws Exception {
int[] a = {1,8,2,6,4,2,3,8,4,6,10,12,45,21,31,22,22,22,23,21,20,23,24,21,23,23};
a = sort(a);
for(int i = 0;i<a.length;i++) {
System.out.print(a[i]+"\t");
}
}
  • 基数排序是稳定排序,在某些时候,基数排序法的效率高于其它的稳定性排序法。
  • 时间复杂度:O(N)
  • 空间复杂度:O(N)

基数排序,计数排序其实是桶排序的特殊情况:

  • 基数排序:根据键值的每位数字来分配桶;
  • 计数排序:每个桶只存储单一键值;
  • 桶排序:每个桶存储一定范围的数值;

整理

十大排序算法总结_java_12

排序算法

最好时间复杂度

最坏时间复杂度

空间复杂度

内部or外部排序

冒泡排序

O(n)

O(n^2)

O(1)

内部排序

选择排序

O(n^2)

O(n^2)

O(1)

内部排序

插入排序

O(n)

O(n^2)

O(1)

内部排序

计数排序

O(n+k)

O(n+k)

O(k)

外部排序

基数排序

O(nk)

O(nk)

O(n+k)

外部排序

桶排序

O(n+k)

O(n^2)

O(k)

外部排序

希尔排序

O(nlogn)

O(1)

内部排序

归并排序

O(nlogn)

O(nlogn)

O(1)

内部排序

快速排序

O(nlogn)

O(n^2)

O(1)

内部排序

堆排序

O(nlogn)

O(nlogn)

O(1)

内部排序