算法-排序算法总结

时间:2022-01-24 09:48:57

插入排序

插入排序思想:每趟将一个元素,按关键字大小插入到它前面已经排序的子序列中,依次重复,知直到插入全部元素。
插入排序分为直接插入排序希尔排序二分插入排序

直接插入排序

在第i趟排序过程中,key = arr[i],arr[0]-arr[i-1]已经有序,从后往前依次将大于key的元素后移,直到遇到小于等于key的元素结束。

代码实现
/** * 直接插入排序,外层循环i代表待插入元素,内层循环j代表待比较元素*/
public static void straightInsert(int[] nums) {
    int len = nums.length;

    for (int i = 1; i < len; i++) {
        int key = nums[i];
        int j = 0;

        for (j = i-1; j >= 0; j--) {
            if (key < nums[j])
                nums[j+1] = nums[j];
            else {
                break;
            }
        }
        nums[j+1] = key;
    }
}
效率分析
时间复杂度O(n^2),空间复杂度O(1)
稳定性分析
由于nums[j]>key是循环进行的判断条件,关键字相等的元素会相遇并进行比较,且排序前后顺序保持不变,所以 直接插入排序是稳定的

希尔排序

将一个数据序列分成若干组,每组由若干相隔一段距离增量的元素组成,在一个组内使用直接插入排序;增量通常设置为数据序列长度的一半,以后每次减半,直到为1。

代码实现
/** * 希尔排序,外层循环delt控制步长,中层循环i代表待插入元素,内层循环j代表待比较元素*/
public static void shellSort(int[] nums) {
    int len = nums.length;

    for (int delt = len / 2; delt > 0; delt /= 2) {
        for (int i = delt; i < len; i++) {
            int key = nums[i];
            int j = 0;

            for (j = i - delt; j >= 0; j -= delt) {
                if (key < nums[j])
                    nums[j+delt] = nums[j];
                else {
                    break;
                }
            }

            nums[j+delt] = key;
        }
    }
}
复杂度分析
时间复杂度O(n*(log(n))^2)),空间复杂度O(1)
稳定性分析
由于增量的存在,希尔排序过程中会错过相邻元素的比较,排序算法不稳定。

交换排序

交换排序分为冒泡排序快速排序

冒泡排序

代码实现
/** * 冒泡排序(升序),外层循环控制排序趟数,内层循环控制当前待比较元素*/
public static void bubbleSort(int[] nums) {
    int len = nums.length;

    for (int i = 1; i < len; i++) {
        for (int j = 0; j < len - i; j++) {
            if (nums[j] > nums[j+1]) {
                int tmp = nums[j];
                nums[j] = nums[j+1];
                nums[j+1] = tmp;
            } else
                continue;
        }
    }
}
复杂度分析
时间复杂度O(n^2),空间复杂度O(1)
稳定性分析
冒泡排序由于相邻元素一定会进行比较,且nums[j] > nums[j+1]作为判断条件,所以是稳定的

快速排序

代码实现
    /** * 快速排序算法*/
public static void quickSort(int[] nums) {
    quickSort(nums, 0, nums.length - 1);
}
private static void quickSort(int[] nums, int start, int end) {
    if (0 <= start && start < end && end < nums.length) {
        int front = start;  // 初始头索引
        int tail = end;     // 初始尾索引
        int key = nums[front];  // 选择当前序列的第一个元素为参考元素

        while (front < tail) {

            //从后向前,找到第一个小于参考元素的索引
            while (nums[tail] >= key && front < tail) {
                tail--;
            }

            //从后面找到了满足条件的元素
            if (front < tail) {
                nums[front++] = nums[tail]; // 移动后面的元素到前面,并更新头指针
            }

            //从前向后,找到第一个大于参考元素的索引
            while (nums[front] <= key && front < tail) {
                front++;
            }

            //找到了满足条件的元素
            if (front < tail) {
                nums[tail--] = nums[front]; // 移动前面的元素到后面,并更新尾指针
            }

        }

        nums[front] = key;

        quickSort(nums, start, front - 1);
        quickSort(nums, tail + 1, end);
    } else
        return;
}
复杂度分析
时间复杂度O(n*log(n)),空间复杂度O(log(n))
稳定性分析
快速排序算法是不稳定的

选择排序

选择排序包含直接选择排序堆排序

直接选择排序

基本思想
第一趟从n个元素的数据中选择最小值,放在最前位置;下一趟从n-1个元素中选出最小值,放在次前位置。以此类推,经过n-1趟完成排序。
代码实现
/** * 直接选择排序*/
public static void straightSelect(int[] nums) {
    int len = nums.length;

    //外层循环趟数,每一趟确定一个最小值;内层循环代表待比较元素
    for (int i = 0; i < len - 1; i++) {
        int min = i;    // 存放最小值索引

        for (int j = i + 1; j < len; j++) {
            if (nums[j] < nums[min])
                min = j;
        }

        //交换
        int tmp = nums[i];
        nums[i] = nums[min];
        nums[min] = tmp;
    }
}
复杂度分析
时间复杂度O(n^2),空间复杂度O(1)
稳定性分析
直接选择排序会对不相邻的元素进行交换,所以是不稳定的

堆排序

代码实现
/** * 堆排序*/
public static void heapSort(int[] nums) {
    int len = nums.length;

    //创建最小堆
    for (int parent = len / 2; parent >= 0; parent--) {
        sift(nums, parent, len - 1);
    }

    //将根节点和最后一个节点交换位置,nums长度-1,然后再次调整堆
    for (int i = len - 1; i > 0; i--) {
        //将根节点与最后一个子节点交换
        {
            int tmp = nums[0];
            nums[0] = nums[i];
            nums[i] = tmp;
        }

        //重新调整堆
        sift(nums, 0, i - 1);
    }
}

private static void sift(int[] nums, int parent, int end) {
    int child = parent * 2 + 1; //左孩子
    int key = nums[parent];     //当前父节点元素

    while (child <= end) {  //当前节点存在子节点
        //判断右孩子是否存在,若存在,child指向较小的孩子
        if (child + 1 < end && nums[child + 1] < nums[child]) {
            child++;
        }

        //判断父节点和当前孩子节点的大小关系
        if (key > nums[child]) {
            //孩子节点上移
            nums[parent] = nums[child];
            //父节点指针下移
            parent = child;
            //更新子节点
            child = parent * 2 + 1;
        } else {
            break;
        }
    }

    nums[parent] = key;
}
复杂度分析
时间复杂度O(n*log(n)),空间复杂度O(1)
稳定性分析
堆排序不稳定

归并排序

归并排序分为自上而下自下而上两种

自上而下

自上而下归并是一种递归的方法,分为两种操作:拆分操作、合并操作

代码实现
/** * 归并排序算法:自顶向下递归排序*/
public class MergeSort2 {
    //合并操作,根据索引合并两个子序列
    public static void merge2(int[] x, int start, int mid, int end) {
        int[] tmp = new int[end - start + 1];
        int index1 = start, index2 = mid + 1, index = 0;

        while (index1 <= mid && index2 <= end) {
            if (x[index1] < x[index2])
                tmp[index++] = x[index1++];
            else
                tmp[index++] = x[index2++];
        }

        while (index1 <= mid)
            tmp[index++] = x[index1++];
        while (index2 <= end)
            tmp[index++] = x[index2++];

        //移动数组元素
        for (int i = 0; i < tmp.length; i++)
            x[start + i] = tmp[i];
    }

    //拆分排序
    public static void sort2(int[] x, int start, int end) {
        if (start < end) {  //递归进行的条件
            int mid = (start + end) / 2;

            sort2(x, start, mid);
            sort2(x, mid + 1, end);
            merge2(x, start, mid, end);
        }
    }

    public static void mergeSort2(int[] x) {
        sort2(x, 0, x.length - 1);
    }
}

自下而上归并

代码实现
/** * 归并排序算法:自底向上归并*/
public class MergeSort {
    //遍历当前数组
    public static void printNums(int[] nums) {
        int len = nums.length;
        StringBuffer sb = new StringBuffer("(");

        int index = 0;
        for (index = 0; index < len - 1; index++)
            sb.append(nums[index] + ",");
        sb.append(nums[len - 1]);
        sb.append(")\n");

        System.out.println(sb.toString());
    }

    //一次归并,合并相邻的子序列
    public static void merge(int[] x, int[] tmp, int begin1, int begin2, int n) {
        int len = x.length;

        int index1 = begin1, index2 = begin2, index = index1;   //索引

        while (index1 < begin1 + n && index2 < begin2 + n && index2 < len) {
            if (x[index1] < x[index2])
                tmp[index++] = x[index1++];
            else
                tmp[index++] = x[index2++];
        }

        while (index1 < begin1 + n && index1 < len)
            tmp[index++] = x[index1++];
        while (index2 < begin2 + n && index2 < len)
            tmp[index++] = x[index2++];
    }

    //一趟归并,合并两两相邻的子序列
    public static void mergePass(int[] x, int n) {
        int len = x.length;
        int[] tmp = new int[len];       //辅助数组

        for (int begin = 0; begin < len; begin += 2*n) {
            merge(x, tmp, begin, begin + n, n);
        }

        //移动数组元素
        for (int index = 0; index < len; index++)
            x[index] = tmp[index];
    }

    //归并排序
    public static void mergeSort(int[] x) {
        int len = x.length;
        for (int n = 1; n < len; n *= 2) {
            mergePass(x, n);
        }
    }
}
复杂度分析
时间复杂度为O(n*log(n)),空间复杂度为O(n)
稳定性分析
归并排序相邻元素会进行比较,排序是稳定的。