您的位置:首页 > 编程语言 > Java开发

七种排序算法的JAVA实现

2016-04-25 18:30 447 查看

七种排序算法的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,有字数限制?

可以到我的网站或简书上看。

我的博客

我的简书
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: