七种排序算法的JAVA实现

七种排序算法的JAVA实现

最近在找工作时很多面试官都会问到排序算法的实现,所以趁着周末有时间就来总结一下七种排序算法实现。
算法的实现我使用的是java语言,其中为了增强算法的可复用性,我使用了泛型这一特性,在排序的数组元
素都要实现Comparable接口,在排序时使用Comparable接口中的方法compareTo来对两个元素进行比较。

### 一、冒泡排序。

在当初大一时候学习C语言,当时教的两种排序算法冒泡排序、简单选择排序印象都很深刻,应该是我学过的
排序算法中最简单的了。

冒泡排序的思想:

1.第一次排序时将序列[0 ~ n - 1]中从前往后进行两个相邻元素的比较,若前者较大则交换,比较n-1次;
当第一趟排序结束时,序列最大的元素就被交换到位置n-1上,就如同一个气泡,一步一步往后翻滚,直到
最后一位。

2.重复步骤1,在第i趟时需要翻滚n-i-1次,每趟决定一个元素的位置,一共需要n-1趟。

比如,初始序列: [1, 5, 4, 3, 2]

第1趟: [1, 4, 3, 2 ] 5

第2趟: [1, 3, 2 ] 4, 5

……

public static <T extends Comparable<T>> void bubbleSort(T[] a) {
    for (int i = 0; i < a.length - 1; i++) {
        for (int j = 0; j < a.length - i - 1; j++) {
            if (a[j].compareTo(a[j + 1]) > 0) {
                T temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }
}

其实优化的冒泡排序应该有第3步,就是在一趟排序中未交换元素则说明子序列已经有序,不在进行下一趟排序;
所以冒泡排序最多执行n-1次。

比如,初始序列:[1, 2, 3, 5, 4]

只需要一趟排序:[1, 2, 3, 4 ] 5

下面这个是我实现的优化冒泡算法,因为多了赋值语句和条件判断,所以在数组前面序列有序的情况下运行时
间会有优化,但是如果数组元素分布均匀,大小随机则反而效率会低下。

public static <T extends Comparable<T>> void bubbleSortOpt(T[] a) {
    for (int i = 0; i < a.length; i++) {
        boolean isSwap = false;
        for (int j = 0; j < a.length - i - 1; j++) {
            if (a[j].compareTo(a[j + 1]) > 0) {
                T temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
                isSwap = true;
            }
        }
        i = isSwap ? i : a.length;
    }
}

而下面这个是在我的数据结构课本上的实现,不使用条件判断跳出循环,解决了效率问题。经过多次测试,下面的排序
算法的运行时间确实比上面两个要好。

public static <T extends Comparable<T>> void bubbleSortOpti(T[] a) {
    int i = a.length - 1, j, last;
    while (i > 0) {
        last = 0;
        for (j = 0; j < i; j++) {
            if (a[j].compareTo(a[j + 1]) > 0) {
                T temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
                last = j;
            }
        }
        i = last;
    }
}

优化冒泡排序在已经有序的情况下只需进行一趟排序,n-1次比较。因此最好情况下的时间复杂度为O(n),无需移动元素;
最坏情况进行n-1趟排序,第i趟比较n-i次,移动元素3(n-i)次,这样总的比较次数为(1/2)n(n-1),移动元素次
数为3n(n-1)/2。最坏情况下时间复杂度为O(n^2)。

### 二、简单选择排序。

简单选择排序的基本思想:

1.第一趟在初始序列[0 ~ n-1]中找最小值元素,并与位置为0的元素交换位置。

2.第i趟在序列[i-1, n-1]中进行,找到最小值元素与位置为i-1的元素交换位置。
每次决定一个元素的位置,经过n-1趟后使得初始序列有序。

比如,初始序列:[1, 5, 4, 3, 2]

第1趟:1 [5, 4, 3, 2]

第2趟:1 2 [4, 3, 5]

……

public static <T extends Comparable<T>> void selectSort(T[] a) {
    for (int i = 0; i < a.length - 1; i++) {
        int k = i;
        for (int j = i + 1; j < a.length; j++) {
            if (a[k].compareTo(a[j]) > 0) {
                k = j;
            }
        }
        if (k > i) {
            T temp = a[i];
            a[i] = a[k];
            a[k] = temp;
        }
    }
}

上面这种方法是标记了最小元素的位置,在一趟排序的最后交换序列首元素和序列最小元素。之前我还写过一种,在写
这篇博客之前我一直以为是冒泡排序,后来才发现是选择排序。我贴出来对比一下,这种写法也是每次确定最小元素的
位置但是没有标记而是在一趟排序时进行多次交换元素,这样做影响了效率不推荐这样做。

public static <T extends Comparable<T>> void selectSortE(T[] a) {
    for (int i = 0; i < a.length - 1; i++) {
        for (int j = i + 1; j < a.length; j++) {
            if (a[i].compareTo(a[j]) > 0) {
                T temp = a[i];
                a[i] = a[j];
                a[j] = temp;
            }
        }
    }
}

简单选择排序和数组序列的初始排列无关,无聊排列如何都必须要执行n-1趟。总的比较次数为n(n-1)/2,
交换元素(n-1)次,移动元素3(n-1)次。最好、最坏和平均时间复杂度都为O(n^2)。

### 三、直接插入排序

直接插入排序的基本思想:

1.向数组序列后面检索,当检索到后一个元素小于前一个元素时,记录下元素。从记录的元素位置开始往前检索,同时元素往后移找到比
记录下元素要小的元素的位置插入。

2.重复1的步骤,经过n-1趟排序后即成为有序序列。

比如,初始序列:[1, 5, 4, 3, 2]

第1趟:[1, (4, 5), 3, 2]

第2趟:[1, (3, 4, 5), 2]

第3趟:[1, (2, 3, 4, 5)]

public static <T extends Comparable<T>> void insertSort(T[] a) {
    for (int i = 1; i < a.length; i++) {
        int j = i;
        T temp = a[i];
        for (; j > 0 && temp.compareTo(a[j - 1]) < 0; j--) {
            a[j] = a[j - 1];
        }
        a[j] = temp;
    }
}

直接插叙排序在有序的情况下,总的比较次数是n-1,移动元素次数是2(n-1),最好情况下时间复杂度就是O(n)。
最坏情况下,一趟最多比较i次,移动元素i+2次,总的比较次数为n(n-1)/2,移动元素次数(n+4)(n-1)/2。最坏情况时间复杂度为O(n^2)。

### 四、快速排序。
快速排序可以说是冒泡排序的高级版本,冒泡排序每次都只能对相邻的两个数进行比较,而快速排序从两端同时检索保证每趟排序结束,
基准数左边的元素都小于基准数,基准数右边的元素都大于基准数。代码中是选中序列第一个元素作为基准,对快排来说还有很多优化的方法,
下次有时间的时候再总结一下。

快速排序的思想:

1.先从序列后端往前检索,找到小于基准数的元素,再从序列前端往后检索,找到大于基准数的数交换两个元素位置。直到从后往前和从前往后的指针相遇,
将基准数的位置和相遇位置元素交换结束一趟排序。这样就划分出两个序列,一个序列的元素比基准数都要小,一个序列的元素比基准数都要大。

2.将划分出的子序列按1的步骤继续排序,直到子序列只有一个元素时得到有序序列。

比如,初始序列:[1, 5, 4, 3, 6, 2]

第1趟:[1, (5, 4, 3, 6, 2)]

第2趟:[1, (2, 4, 3), 5, (6)]

第3趟:[1, 2, (4, 3), 5, 6]

第4趟:[1, 2, 3, 4, 5, 6]

public static <T extends Comparable<T>> void quickSort(T[] a) {
    quickSort(a, 0, a.length - 1);
}
public static <T extends Comparable<T>> void quickSort(T[] a, int left, int right) {
    if (left >= right) return;
    T temp = a[left];
    int i = left;
    int j = right;
    while (i < j) {
        while(a[j].compareTo(temp) >= 0 && i < j) j--;
        while(a[i].compareTo(temp) <= 0 && i < j) i++;
        if (i < j) {
            T t = a[i];
            a[i] = a[j];
            a[j] = t;
        }
    }
    a[left] = a[j];
    a[j] = temp;
    quickSort(a, left, j - 1);
    quickSort(a, j + 1, right);
}

快排最坏情况是每次分割的两个子序列中有一个为空,机初始序列有序(顺序或逆序),这是快速排序效率最低,时间复杂度为O(n^2)。
快排的平均时间复杂度为O(nlogn)。
在最坏情况下快排需要的附加堆栈存储空间为O(n)。

### 五、堆排序。
使用数据结构堆来辅助我们排序,如果要构造非递减序列我们采用大根堆。
大根堆是包含n个结点的完全二叉树,该树中每个节点的关键字值小于等于其双亲节点的关键字值。

堆排序的思想:

1.将初始序列构造成大根堆,从堆中第一个非叶子节点开始调用adjustDown方法将元素向下调整,直到堆顶。

2.第i趟排序将堆顶元素a[0]与堆底元素a[n-i]交换,然后将a[0]向下调整,直到堆中只剩最后一个元素。

比如,初始序列:[1, 5, 4, 3, 2]

建堆后:[5, 3, 4, 1, 2]

第1趟:[4, 3, 1, 2] 5

第2趟:[3, 1, 2] 4, 5

第3趟:[1, 2] 3, 4, 5

……

public static <T extends Comparable<T>> void heapSort(T[] a) {
    for (int i = (a.length - 2) / 2; i >= 0; i--) {
        adjustDown(a, i, a.length - 1);
    }
    for (int i = a.length - 1; i >= 0; i--) {
        T temp = a[0];
        a[0] = a[i];
        a[i] = temp;
        adjustDown(a, 0, i - 1);
    }
}
public static <T extends Comparable<T>> void adjustDown(T[] a, int r, int j) {
    T temp = a[r];
    int child = 2 * r + 1;
    while (child <= j) {
        if (child < j && a[child].compareTo(a[child + 1]) < 0) child++;
        if (temp.compareTo(a[child]) >= 0) break;
        a[(child - 1) / 2] = a[child];
        child = 2 * child + 1;
    }
    a[(child - 1) / 2] = temp;
}

构建堆得时间最多是O(nlogn),在每趟排序后都要向下调整一次最多花费O(nlogn),所以堆排序的时间复杂度为O(nlogn)。

不习惯在CSDN使用markdown,有字数限制?
可以到我的网站或简书上看。
我的博客
我的简书

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值