为了方便打印数组,我自己先写了一个抽象类来打印数组,之后具有排序功能的类均继承此类。
public abstract class PrintArray {
public void printArray(int[] arr){
System.out.println("数组的长度为:"+arr.length+".");
int i = 0;
for(int element : arr){
System.out.println(++i+"-----"+element);
}
}
}
对于通过遍历来实现排序的方法而言,有一个前提一定需要记住:一个N个数的序列,只需要N-1趟遍历排序就可以将这些数全部排序。
一、插入排序
一个N个元素的序列排序,插入排序由N-1趟排序组成,每趟排序从后往前扫描,对于P=1趟到P=N-1趟,每趟排序结束后,插入排序保证位置0到位置P上的元素已为排序状态,这也意味着,进入第P趟时(此时P-1趟已经结束),0到位置P-1上(包含P-1)的元素均已有序,第P趟结束后,位置0到位置P(包含P)的元素均已有序,下面是插入排序的java代码,偷懒写到了一个方法里。
import org.junit.Test;
public class InsertSort extends PrintArray{
@Test
public void insertSort(){
int[] arr = { 42, 67, 4, 1, 24, 32, 65, 24, 23, 73, 36, 11, 28 };
int temp, p, i;
//temp缓存第P趟待插入的元素
//注意这里需要把变量i定义在for循环外面,因为i在for循环外面要使用
for(p=1;p<arr.length;p++){
//arr[0]不需要比较,也就是说P是从P=1开始的
temp = arr[p];
for(i=p; i>0 && arr[i-1] > temp; i--){
//注意这里i=p,表示从后往前扫描
//如果改成i=p-1,后面的代码需要更改
//同时注意判断条件
//如果已有序序列里的元素比arr[p]大,则这个元素往后移动
//这里没有使用数据交换,而是移动的写法,很巧妙
arr[i] = arr[i-1];
}
arr[i] = temp;//最后的i即为temp应该插入的数组下标
}
//打印结果
System.out.println("插入排序");
printArray(arr);
}
}
//插入排序
//数组的长度为:13.
//1-----1
//2-----4
//3-----11
//4-----23
//5-----24
//6-----24
//7-----28
//8-----32
//9-----36
//10-----42
//11-----65
//12-----67
//13-----73
插入排序原理可用一句话概括:从后向前扫描已排序序列。不过插入排序是一种二次时间的排序算法,也就是说其时间复杂度为O(N2)。这个也比较好分析:
对于每个P,即每趟排序,arr[i] = arr[i-1]最多执行P+1次,对所有P求和,有
2+3+…+N = O(N 2)
对于一个已经预先排序的序列,插入排序的运行时间为O(N),因为内层循环并不执行,所以如果是那种几乎被排序的序列,插入排序会很快。
二、冒泡排序
冒泡排序是一种经典的排序算法,总体的思想是从前向后扫描数组,每次比较两个临近的数字,如果前一个数比后一个数大(小),则交换两个数的位置,最后将大的数(小的数)后移到数组底部,得到原数组的顺序(逆序)排序。这样的过程是将未排序序列里的最大(最小)数冒泡到数组最后,所以叫冒泡法。
冒泡排序的方法是:首先进行第一遍排序,从第一个元素开始,将相邻的两个元素比较,较小(较大)的数字放在前面,大(小)的数字放在后面,依次往后比较,比较到最后一组后,产生一个最大值;第二遍排序是除去产生的最大值,对剩下的序列进行类似于第一遍排序的方法。以此进行N-1遍排序就能将N个数按照从小到大(从大到小)排序。
import org.junit.Test;
public class BubbleSort extends PrintArray {
@Test
public void bubbleSort() {
int[] arr = { 42, 67, 4, 1, 24, 32, 65, 24, 23, 73, 36, 11, 28 };
for (int i = 0; i < arr.length - 1; i++) {
//注意最外层是N-1趟
for (int j = 0; j < arr.length - i - 1; j++) {
//因为冒泡法是将最值放到数组尾,所以内层必须从0元素开始遍历
//同时每趟都会减少一个需要遍历的元素,所以j<arr.length - i - 1
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
System.out.println("冒泡排序");
printArray(arr);
}
}
//冒泡排序
//数组的长度为:13.
//1-----1
//2-----4
//3-----11
//4-----23
//5-----24
//6-----24
//7-----28
//8-----32
//9-----36
//10-----42
//11-----65
//12-----67
//13-----73
三、选择排序
选择排序的原理是:每次扫描未排序的序列,从中选择最小的元素,放到以排序部分的末尾。实现的时候有两种情况,一种是一边比较一边交换,另一种是一边找一边设立标记,最后更新未排序序列里的第一个元素,即将最小数与未排序序列里的第一个元素交换。
以第二种实现方法为例:确定第一个数为基准数,认为他是最小数,然后依次在所有其他数中找到比他小的数,如有则交换,这样就找到第一个最小数。第二遍排序是对除第一个数据外的数据序列进行选择排序,以此类推共进行N-1遍排序,就能将N个数排序。
import org.junit.Test;
public class SelectionSort extends PrintArray {
@Test
public void selectionSort() {
int[] arr = { 42, 67, 4, 1, 24, 32, 65, 24, 23, 73, 36, 11, 28 };
int iter, position, j, temp;
for (iter = 1; iter < arr.length; iter++) {
position = iter - 1;
//position记录未排序序列里最小元素的位置
//每趟排序默认是未排序序列的第一个元素arr[iter-1]
for (j = iter; j < arr.length; j++) {
if (arr[position] > arr[j]) {
position = j;
}
}
//遍历完剩余未排序序列后,最后更新未排序序列里的第一个元素
temp = arr[position];
arr[position] = arr[iter - 1];
arr[iter - 1] = temp;
}
System.out.println("快速排序:做标记方式");
printArray(arr);
}
@Test
public void otherSelectionSort() {
int[] arr = { 42, 67, 4, 1, 24, 32, 65, 24, 23, 73, 36, 11, 28 };
int i, j, temp;
for (i = 0; i < arr.length - 1; i++) {
//一共是N-1趟遍历排序
for (j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[i]) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
System.out.println("快速排序:边找边交换方式");
printArray(arr);
}
}
//快速排序:做标记方式
//数组的长度为:13.
//1-----1
//2-----4
//3-----11
//4-----23
//5-----24
//6-----24
//7-----28
//8-----32
//9-----36
//10-----42
//11-----65
//12-----67
//13-----73
//快速排序:边找边交换方式
//数组的长度为:13.
//1-----1
//2-----4
//3-----11
//4-----23
//5-----24
//6-----24
//7-----28
//8-----32
//9-----36
//10-----42
//11-----65
//12-----67
//13-----73
四、希尔排序
这里先给出代码
import org.junit.Test;
public class ShellSort extends PrintArray {
@Test
public void shellSort() {
int[] arr = { 42, 67, 4, 1, 24, 32, 65, 24, 23, 73, 36, 11, 28 };
int temp, increment, i, j;
for (increment = arr.length / 2; increment > 0; increment /= 2) {
for (i = increment; i < arr.length; i++) {
temp = arr[i];
for (j = i; j >= increment; j -= increment) {
if (temp < arr[j - increment]) {
arr[j] = arr[j - increment];
} else {
break;
}
}
arr[j] = temp;
}
}
System.out.println("希尔排序");
printArray(arr);
}
}
五、快速排序
这里先给出代码,后面补上说明。
//改进版
import org.junit.Test;
public class QuickSort extends PrintArray {
/**
* 三数取中值
*/
public int median3(int[] arr, int left, int right) {
int median = (left + right) / 2;
if (arr[left] > arr[median]) {
exchangeElement(arr, left, median);
}
if (arr[left] > arr[right]) {
exchangeElement(arr, left, right);
}
if (arr[median] > arr[right]) {
exchangeElement(arr, right, median);
}
//把主元隐藏,这样的话,前面right-1个子序列不再含有主元
//且这right-1个元素需要与主元比较
exchangeElement(arr, right - 1, median);
return arr[right - 1];
}
//交换数组两元素
public void exchangeElement(int[] arr, int index1, int index2) {
// 不做安全性检查
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
public void quickSort(int[] arr, int left, int right) {
int i, j, pivot;
//使用median3方法时,子数组至少要有4个元素才能进行快速排序
//所以这里必须要有这个大判断条件
if (right - left > 2) {
//获取主元
pivot = median3(arr, left, right);
//子序列从left开始到主元所在的位置,而不是left后一个位置开始到主元前一个位置结束
i = left;
j = right - 1;
for (;;) {
while (arr[++i] < pivot) {
}
while (arr[--j] > pivot) {
}
if (i < j) {
exchangeElement(arr, i, j);
} else {
break;
}
}
//i、j错开后,将主元与arr[i]交换位置,交换后,主元就被放到了正确的位置上
exchangeElement(arr, i, right - 1);
//递归对左右子序列排序
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
} else {
//此时,right-left只可能有三个可能值:0,1,2
//不能再用快速排序,随便怎么排序都可以
if (arr[left] > arr[right])
exchangeElement(arr, left, right);
if (right - left == 2) {
int mid = (right + left) / 2;
if (arr[mid] > arr[right]) {
exchangeElement(arr, mid, right);
}
if (arr[mid] < arr[left]) {
exchangeElement(arr, mid, left);
}
}
}
}
@Test
public void quick_sort() {
int[] arr = { 42, 67, 4, 1, 24, 32, 65, 24, 23, 73, 36, 11, 28 };
quickSort(arr, 0, arr.length - 1);
printArray(arr);
}
}
//数组的长度为:13.
//1-----1
//2-----4
//3-----11
//4-----23
//5-----24
//6-----24
//7-----28
//8-----32
//9-----36
//10-----42
//11-----65
//12-----67
//13-----73
//基本版
import org.junit.Test;
public class BasicQuickSort extends PrintArray {
public void basicQuickSort(int[] array, int start, int end) {
if (start < end) {
int key = array[start];// 初始化保存基元
int i = start, j;// 初始化i,j
for (j = start + 1; j <= end; j++) {
if (array[j] < key)// 如果此处元素小于基元,则把此元素和i+1处元素交换,并将i加1,如大于或等于基元则继续循环
{
int temp = array[j];
array[j] = array[i + 1];
array[i + 1] = temp;
i++;
}
}
array[start] = array[i];// 交换i处元素和基元
array[i] = key;
basicQuickSort(array, start, i - 1);// 递归调用
basicQuickSort(array, i + 1, end);
}
}
@Test
public void myTest(){
int[] arr = { 42, 67, 4, 1, 24, 32, 65, 24, 23, 73, 36, 11, 28 };
basicQuickSort(arr, 0, arr.length - 1);
printArray(arr);
}
}
//数组的长度为:13.
//1-----1
//2-----4
//3-----11
//4-----23
//5-----24
//6-----24
//7-----28
//8-----32
//9-----36
//10-----42
//11-----65
//12-----67
//13-----73
六、堆排序
堆排序整个过程利用了堆这种数据结构能够获取最大元素(最小元素)的性质。
堆排序本质上使用的是堆的调整过程,也就是如果一棵树的左右子树都已经是堆了的话,调整根结点的过程。如果需要获得序列的顺序排序,那么先创建最大堆,然后使用删除堆顶元素的思路,与删除操作不同的是这里会把删除的堆顶元素逆序放到数组的末尾;如果需要获得序列的逆序排序,那么先创建最小堆,然后使用删除堆顶元素的思路,同样与删除操作不同的是这里也会把删除的堆顶元素逆序放到数组的末尾,具体代码如下:
import org.junit.Test;
public class HeapSort extends PrintArray {
/**
* 左右子树已经是最大堆,调整根结点的操作<br>
* 使用下滤操作<br>
* 注意:这里的堆是从数组下标为0的地方开始的,而一般的堆在数组里时,下标为0的位置存放的是哨兵<br>
* 所以左子结点的位置为根结点的位置*2+1
*
* @param arr
* 待调整的子树,数组形式
* @param parent
* 子树的父结点在数组里的位置
* @param size
* 整个堆的大小
*/
public void percDown(int[] arr, int parent, int size) {
int tmp;
int child;// 较大子结点的位置,默认为左子结点
for (tmp = arr[parent]; 2 * parent + 1 < size; parent = child) {
child = 2 * parent + 1;// 左子结点的位置
// 判断左右子结点的较大值,位置为child
if (child + 1 < size && arr[child + 1] > arr[child]) {
child++;
}
if (tmp < arr[child]) {
arr[parent] = arr[child];
} else {
break;
}
}
arr[parent] = tmp;
}
@Test
public void HeapSort() {
//使用堆排序的时候
//如果最终需要数组的顺序排序,那么先创建最大堆,然后按删除元素的思想调整数组
//如果最终需要数组的逆序排序,那么先创建最小堆,然后按删除元素的思想调整数组
int[] arr = { 42, 67, 4, 1, 82, 123, 29, 18, 24, 32, 65, 24, 23, 73, 36, 11, 28 };
// 创建最大堆
for (int i = (arr.length - 1) / 2; i >= 0; i--) {
percDown(arr, i, arr.length);
}
// 按照删除堆顶元素的思想对数组排序,生成最终的顺序序列
for (int i = arr.length - 1; i > 0; i--) {
//从堆的尾部开始与堆顶元素交换
int tmp = arr[i];
arr[i] = arr[0];
arr[0] = tmp;
//调整新堆
percDown(arr, 0, i);
}
System.out.println("堆排序:");
printArray(arr);
}
}
//堆排序:
//数组的长度为:13.
//1-----1
//2-----4
//3-----11
//4-----23
//5-----24
//6-----24
//7-----28
//8-----32
//9-----36
//10-----42
//11-----65
//12-----67
//13-----73