菜鸟笔记
提升您的技术认知

java实现十大排序算法-ag真人游戏

十大排序算法分别为:冒泡排序快速排序插入排序希尔排序选择排序、堆排序、归并排序、桶排序、计数排序、基数排序

他们的时间复杂度分别为:

关于时间复杂度

平方阶 (o(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。

线性对数阶 (o(nlog2n)) 排序 快速排序、堆排序和归并排序;

o(n1 §)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序

线性阶 (o(n)) 排序 基数排序,此外还有桶、箱排序。

关于稳定性

稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。

不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。

名词解释:

  • n:数据规模
  • k:"桶"的个数
  • in-place:占用常数内存,不占用额外内存
  • out-place:占用额外内存
  • 稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同

冒泡排序

冒泡排序是基于交换排序的典型, 是快速排序思想的基础,是一种稳定的排序算法。它的时间复杂度为o(n2)

思路:循环遍历多次每次从前往后把大元素往后调,每次确定一个最大(最小)元素,多次后达到排序序列。(或者从后向前把小元素往前调)。

实现方式:

public static void bubble(int[] arr) {
  
        for (int i = 1; i < arr.length; i  ) {
  
            // 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。
            boolean flag = true;
            for (int j = 0; j < arr.length - i; j  ) {
  
                if (arr[j] > arr[j   1]) {
  
                    arr[j] = arr[j]   arr[j   1];
                    arr[j   1] = arr[j] - arr[j   1];
                    arr[j] = arr[j] - arr[j   1];
                    flag = false;
                }
            }
            if (flag) {
  
                break;
            }
        }
    }

快速排序

快速排序是对冒泡排序的一种改进,采用递归分治的方法进行求解。而快排相比冒泡是一种不稳定排序,时间复杂度最坏是o(n2),平均时间复杂度为o(n log n),最好情况的时间复杂度为o(n log n)。

对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

思路:

  • 从数列中挑出一个元素,称为 “基准”(pivot);

  • 分区:将比这个数大或等于的数放在它的右边,小于它的数放在它的左边

  • 再对左右区间重复第二步,直到各区间只有一个数

具体步骤:选择一个基准数,这个基准数的位置记为第一个坑 从后往前寻找比他小的数字,找到的第一个比他小的数字则挖走填入到前一个坑中,此时挖走数据,则形成了一个新的坑,然后从前往后找比他大的数,找到的第一个也填到上一个挖走数字的坑中。以此类推,直到无法再查询。

注意:每找到一个数,就换一个方向,并且把这个数字填到上一次挖出的坑中

实现方法:

class quicksort {
  
    public void quicksort(int[] arr, int start, int end) {
  
        // 找出分左右两区的位置索引位置,然后对左右两区进行递归调用
        if (start < end) {
  
            int index = getindex(arr, start, end);
            // 对左区递归
            quicksort(arr, start, index - 1);
            // 对右区递归
            quicksort(arr, index   1, end);
        }
    }
    private static int getindex(int[] arr, int start, int end) {
  
        int i = start;
        int j = end;
        int x = arr[i];
        while (i < j) {
  
            // 从后往前找,如果找出来的数比基准数大,则让 j--
            while (i < j && arr[j] >= x) {
  
                j--;
                // 当 j

插入排序

插入排序是一种最简单的排序方法,他是将一个数据插入到一个长度为m的有序表中,并且让他保持有序性

从1索引处开始,将后面的元素插入到之前的有序列表中,并且使之保持有序。

class fastsort {
  
    // 外层循环定义轮次
    public void fastsort(int[] arr) {
  
        // 因为是从1索引处开始插入,所以这里 i 要从1开始
        for (int i = 1; i < arr.length; i  ) {
  
            // 里层循环进行比较插入
            int j = i;
            while (j > 0 && arr[j] < arr[j - 1]) {
  
                arr[j] = arr[j]   arr[j - 1];
                arr[j - 1] = arr[j] - arr[j - 1];
                arr[j] = arr[j] - arr[j - 1];
                j--;
            }
        }
    }
}
// 或者
class fastsort {
  
    // 外层循环定义轮次
    public void fastsort(int[] arr) {
  
        // 因为是从1索引处开始插入,所以这里 i 要从1开始
        for (int i = 1; i < arr.length; i  ) {
  
            // 里层循环进行比较插入
            for (int j = i; j > 0; j--) {
  
                if (arr[j] < arr[j - 1]) {
  
                    arr[j] = arr[j]   arr[j - 1];
                    arr[j - 1] = arr[j] - arr[j - 1];
                    arr[j] = arr[j] - arr[j - 1];
                }
            }
        }
    }
}

希尔排序

希尔排序又称缩小增量排序,他是对插入排序的一个优化。直接插入排序实际上就是增量为1的希尔排序。

思路:先将原数组按照增量(ht)分组,每个子序列按照直接插入法排序。排好之后用下一个增量 ht/2 再将数组分为子序列,再使用直接插入法排序,直到 增量为1的时候,整个序列排序完成。经过第一轮排序,会让数组大致有序,再进行细致的排序

希尔排序的关键在于怎样选择一个合适的增量,第一次排序可以设置增量为数组长度的一半

d就是增量,每间隔为5的两个元素组成一组,每一趟排序,将每组元素中小的元素放前面,大的元素放后面(前者大则两者位置互换,前者小则不做操作)

实现方法

// 设置增量为数组一半时的方法
class shellsort {
  
    public void shellsort(int[] arr) {
  
        // 定义ht为首次排序的增量,长度为数组的一半
        for (int ht = arr.length / 2; ht > 0; ht /= 2) {
  
            for (int i = ht; i < arr.length; i  ) {
  
                for (int j = i; j > ht - 1; j -= ht) {
  
                    if (arr[j] < arr[j - ht]) {
  
                        swap(arr, j, j - ht);
                    }
                }
            }
        }
    }
    public void swap(int[] arr, int i, int j) {
  
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

有的时候,增量设置为数组长度的一半也不是很好,这时我们就可以使用克努特序列

克努特序列( knuth 序列 ):数组从1 开始,通过递归表达式 h = h*3 1 来产生。倒推公式则为h = ( h - 1 ) / 3

优化后的算法:

class shellsort {
  
    public void shellsort(int[] arr) {
  
        // 通过克努特序列来确定增量
        int ht = 1;
        // 注意此处要让数组长度除以3
        while (ht <= arr.length / 3) {
  
            // 克努特序列思想
            ht = ht * 3   1;
        }
        // 缩小增量则是反向的克努特序列式子
        for (int h = ht; h > 0; h = (h - 1) / 3) {
  
            for (int i = h; i < arr.length; i  ) {
  
                for (int j = i; j > h - 1; j -= h) {
  
                    if (arr[j] < arr[j - h]) {
  
                        swap(arr, j, j - h);
                    }
                }
            }
        }
    }
    public void swap(int[] arr, int i, int j) {
  
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

选择排序

思路:从0索引处开始,依次和后面的元素进行比较 ,小的元素往前放,经过一轮比较后,最小的元素就放在了最小索引处,每经过一轮比较,索引位置 1。

注意:换完位置之后,比较不停止,而是使用交换了位置的元素继续比较,直到比较到最后一个

例如:13 69 80 57 24 。假设此时进行第二轮比较,即从69开始

69比57大,则互换位置,注意此时:继续用索引为1 位置的数字进行比较,即用57继续往后比较,57比24大,互换位置。

也就是说,比较只有到达数组末尾才会结束,互换位置之后继续用当前索引位置的数字进行比较。数组长度为n,则要比较 n-1 轮,

实现方法:

class choosesort {
  
    public void choosesort(int[] arr) {
  
        // 第一轮比较,从0索引处开始比较,只要比较 length - 1 轮
        // 每比较完一轮,index  
        for (int index = 0; index < arr.length - 1; index  ) {
  
            // 直接与索引处下一个数字进行比较,所以 i = index   1
            for (int i = 1   index; i < arr.length; i  ) {
  
                if (arr[index] > arr[i]) {
  
                    swap(arr, index, i);
                }
            }
        }
    }
    public void swap(int[] arr, int i, int j) {
  
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

堆排序

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序

思路:

  • 将需要排序的序列构造成一个大顶堆(大根堆),此时,整个序列的最大值就是堆顶的根节点
  • 将其与末尾元素进行交换,此时末尾就为最大值
  • 然后将剩余 n-1 个元素重新构造成一个堆,这样会得到这 n 个元素的最小值
  • 反复执行,直到有序

建立大顶堆时,是从最后一个非子叶节点开始从下往上调整的

大顶堆结构的特点:每一个节点的值都大于或等于他左右孩子节点的值

如果数组长度为n,那么最后一个非子叶节点的位置就为 n/2 - 1。

在构建大顶堆的时候,因为最后要将最大的元素与当前轮的末尾元素进行交换,交换之后很可能剩下的元素就不满足大顶堆结构了,所以要递归调用构造大顶堆的方法,直到全部形成大顶堆结构。

每一轮构造大顶堆结构完成,最大元素放到末尾,下一轮的构造,这个元素就不参与了。所以下一轮的末尾元素位置为参与了构造大顶堆结构的数据位置。

详细理解:假设数组为 7,3,8,5,1,2,他的原始结构为

经过第一轮的构建,以及数字的交换,变成如下结构

此时,8为已经换到末尾的元素,那么下一轮他就不参与构建,所以下一轮的末尾元素就位置就变成了 1 所处的位置

关于堆排序的详细参考

数组可以看作是一个完全二叉树,数组从逻辑上来说就是一个堆结构

相让数组升序则使用大顶堆,相让数组降序则使用小顶堆。

用公式描述堆的定义

  • 大顶堆:

    arr[i] >= arr[2i 1] && arr[i] >= arr[2i 2];
    
  • 小顶堆:

    arr[i] <= arr[2i 1] && arr[i] <= arr[2i 2];
    

实现方法:

public class testb {
  
    public static void main(string[] args) {
  
        maxheapsort maxheapsort = new maxheapsort();
        int arr[] = {
  1, 0, 6, 7, 2, 3, 4, 53, 76, 1, 35, 23, -6, -32, -2, 45};
        // 调用方法把数组调成大顶堆
        maxheapsort.firstconstructheap(arr);
        system.out.println(arrays.tostring(arr));
        // 经过以上操作,数组已经变成大顶堆,此时把根元素与最后一个元素进行交换
        maxheapsort.changevalue(arr);
        system.out.println(arrays.tostring(arr));
    }
}
class maxheapsort {
  
    // 第一次构建大顶堆的方法
    public void firstconstructheap(int[] arr) {
  
        // 定义开始调整的位置(通过公式来设定),位置为最后一个非叶子节点
        int startindex = arr.length / 2 - 1;
        // 循环开始调整位置,下一个非叶子节点就是 当前坐标 - 1
        for (int i = startindex; i >= 0; i--) {
  
            maxheapsort(arr, arr.length, i);
        }
    }
    public void changevalue(int[] arr) {
  
        for (int i = arr.length - 1; i > 0; i--) {
  
            // 进行调换
            swap(arr, 0, i);
            // 调换完之后,把剩余元素调换为大顶堆
            // 要重新获取左右节点,所以index传入0
            maxheapsort(arr, i, 0);
        }
    }
    /**
     * @param arr   要进行排序的数组
     * @param size  需要调整的元素个数
     * @param index 从哪里开始调整
     */
    // 构建大顶堆
    public void maxheapsort(int[] arr, int size, int index) {
  
        // 先获取左右子节点的索引,由堆的公式定义获得
        int leftnodeindex = index * 2   1;
        int rightnodeindex = index * 2   2;
        // 查找最大节点对应的索引,先让他等于开始索引位置,再进行比较
        int maxnodeindex = index;
        // 如果左节点的数字大于它,则互换索引
        // 由于有递归调用,索引为不断增加,添加条件防止下标越界
        if (leftnodeindex < size && arr[leftnodeindex] > arr[maxnodeindex]) {
  
            maxnodeindex = leftnodeindex;
        }
        if (rightnodeindex < size && arr[rightnodeindex] > arr[maxnodeindex]) {
  
            maxnodeindex = rightnodeindex;
        }
        // 如果最大索引位置不等于原来的位置,说明有比他大的数字
        // 此时将最大数字移动到顶端
        if (maxnodeindex != index) {
  
            swap(arr, maxnodeindex, index);
            // 换完位置后,可能下面的子树就不满足大顶堆结构了,所以要递归调用
            maxheapsort(arr, size, maxnodeindex);
        }
    }
    public void swap(int[] arr, int i, int j) {
  
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

归并排序

归并排序的原理:假设初始序列有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到 n/2 个长度为2或1的有序子序列,再两两归并,重复步骤直到得到一个长度为n的有序序列为止。这种排序方法称为二路归并排序,是归并排序中用的最多的方法 。

如果数组中只有一个元素,这个数组是有序的。

或者更直观的来看:

实现方法:

public class testb {
  
    public static void main(string[] args) {
  
        mergesort mergesort = new mergesort();
        int arr[] = {
  1, 4, 6, 8, 5, 3, 9, 34, 65, 23, 13, 84, -3, -4, -7, -1};
        mergesort.spilt(arr, 0, arr.length - 1);
        system.out.println(arrays.tostring(arr));
    }
}
class mergesort {
  
    // 拆分方法
    public void spilt(int[] arr, int startindex, int endindex) {
  
        // 计算中间索引
        int centerindex = (startindex   endindex) / 2;
        // 如果起始索引小于结束索引,则继续拆分,直到不能再拆为止
        if (startindex < endindex) {
  
            // 对左边继续拆分,左边序列的结束索引为中间点
            spilt(arr, startindex, centerindex);
            // 对右边继续拆分,右边序列的起始索引为中间点   1
            spilt(arr, centerindex   1, endindex);
            // 拆分完成,进行归并
            mergesort(arr, startindex, centerindex, endindex);
        }
    }
    /**
     * @param arr         原数组
     * @param startindex  左边数组的起始索引
     * @param centerindex 中间索引
     * @param endindex    右边索引的结束索引
     */
    // 归并方法
    public void mergesort(int[] arr, int startindex, int centerindex, int endindex) {
  
        // 定义一个临时数组,由于要多次归并,所以数组长度是一直在变的,所以长度要设置为endindex-startindex 1
        int[] temparr = new int[endindex - startindex   1];
        // 定义左边数组的起始索引
        int i = startindex;
        // 定义右边数组的起始索引(中间索引   1)
        int j = centerindex   1;
        int index = 0;
        // 比较左右两个数组的元素大小,然后往临时数组中存放
        while (i <= centerindex && j <= endindex) {
  
            if (arr[i] <= arr[j]) {
  
                temparr[index] = arr[i];
                i  ;
            } else {
  
                temparr[index] = arr[j];
                j  ;
            }
            index  ;
        }
        // 由于数组长度奇偶不定,所以可能会存在剩余元素
        // 处理剩余元素
        // i<=centerindex,说明左边元素有剩余
        while (i <= centerindex) {
  
            temparr[index] = arr[i];
            i  ;
            index  ;
        }
        // j<=endindex,说明左边元素有剩余
        while (j <= endindex) {
  
            temparr[index] = arr[j];
            j  ;
            index  ;
        }
        // 把临时数组赋给原数组
        for (int k = 0; k < temparr.length; k  ) {
  
            arr[k   startindex] = temparr[k];
        }
    }
}

桶排序

思路:将待排序的序列分到若干个桶中,每个桶内的元素再进行个别排序

实现方法:

public class bucketsort {
  
	public static void main(string[] args) {
  
		int a[]= {
  1,8,7,44,42,46,38,34,33,17,15,16,27,28,24};
		list[] buckets=new arraylist[5];
		for(int i=0;i();
		}
		for(int i=0;i

计数排序

计数排序是一种特殊的桶排序

思路:

  • 找出待排序数组中的最大值和最小值
  • 统计数组中每个值为 i 的元素出现的,存入数组c的第 i 项
  • 对所有的计数累加(从 c 中的第一个元素开始,每一项和前一项相加)
  • 反向填充目标数组:将每个元素 i 放在新数组的第 c( i )项,每放一个元素就将c( i )减去1。

实现方法:

class countingsort{
  
    public int[] countingsort(int[] arr) {
  
        int maxvalue = getmaxvalue(arr);
        int bucketlen = maxvalue   1;
        int[] bucket = new int[bucketlen];
        for (int value : arr) {
  
            bucket[value]  ;
        }
        int sortedindex = 0;
        for (int j = 0; j < bucketlen; j  ) {
  
            while (bucket[j] > 0) {
  
                arr[sortedindex  ] = j;
                bucket[j]--;
            }
        }
        return arr;
    }
    public int getmaxvalue(int[] arr) {
  
        int maxvalue = arr[0];
        for (int value : arr) {
  
            if (maxvalue < value) {
  
                maxvalue = value;
            }
        }
        return maxvalue;
    }
}

基数排序

基数排序不需要对关键字进行比较,只需要对关键字进行“分配”与“收集“两种操作即可完成

思路:

创建10个桶,分别给定编号 0 - 9。对数组进行排序,第一轮比较,从数组顺序取出数字,每取出一个,放到编号与其个位的数字相对应的桶中,直到遍历完数组。这些桶可以看成是数组。

然后依次从 0 - 9 号桶取出存入的数字,组成新数组,进行第二轮比较,从数组顺序取出数字,每取出一个,放到编号与其十位的数字相对应的桶中,直到遍历完数组。

以此类推,数组中最大的数字为几位数,那就需要进行几轮

例如:一个数组为:{ 11,23,59,1,4,9,7,123,76,98,187,132,98,78,234 }

那么第一轮比较之后再取出的数组为:{ 11,1,132,23,123,4,234,76,7,187,98,98,78,59,9 }

第二轮比较之后取出的数组为:{ 1,4,7,9,11,23,123,132,234,59,76,78,187,98,98 }

第三轮比较之后取出的数组为:{ 1,4,7,9,11,23,59,76,78,98,98,123,132,187,234 }

此时数组排序完成

实现方法:

class basesort {
  
    // 获取最大值的方法,用来确定需要比较的轮数
    public int getmax(int[] arr) {
  
        int max = arr[0];
        // 因为max为arr[0],所以从 i = 1 开始比较
        for (int i = 1; i < arr.length; i  ) {
  
            if (max < arr[i]) {
  
                max = arr[i];
            }
        }
        return max;
    }
    public void basesort(int[] arr) {
  
        int max = getmax(arr);
        // 定义二维数组,放十个桶.每个桶最大长度是 arr.length
        // 即:全部数字的个位都一样,此时桶达到最大长度
        int[][] temparr = new int[10][arr.length];
        // 定义一个统计数组,用来统计每个桶中放了多少个元素
        // 由于有10个桶,所以统计数组长度也为10
        int[] count = new int[10];
        // 比较轮次
        int len = string.valueof(max).length();
        // k为除以的数字,求个位要除以1,求十位要除以10,依次类推
        for (int i = 0, k = 1; i < len; i  , k *= 10) {
  
            // 获取每个位上的数字
            for (int j = 0; j < arr.length; j  ) {
  
                // remainder(余数): 为每个位上数字
                int remainder = arr[j] / k % 10;
                // count[remainder]   表示相应的桶中存入了一个数据
                temparr[remainder][count[remainder]  ] = arr[j];
            }
            // 依次取出桶中的元素
            int index = 0;
            for (int m = 0; m < count.length; m  ) {
  
                // 如果该位置不为0,说明这个桶中有元素
                if (count[m] != 0) {
  
                    for (int h = 0; h < count[m]; h  ) {
  
                        arr[index] = temparr[m][h];
                        index  ;
                    }
                    // 每取出一个桶,则清除他统计的个数,因为下一轮还要存入
                    count[m] = 0;
                }
            }
        }
    }
}

注意:这里的算法只能排序正数,如果要排序负数,我们可以定义19个桶,十个为 0 - 9,还有九个桶为 (-1)- (-9)

网站地图