1.1排序算法概述
排序算法分类:内部排序和外部排序。
内部排序:整个排序过程不需要借助于外部存储器(如磁盘等),所有排 序操作都在内存中完成。
外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排 序过程放在内存中完成,必须借助于外部存储器(如磁盘)。外部排序最 常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。
十大内部排序算法
选择排序
直接选择排序、堆排序
交换排序
冒泡排序、快速排序
插入排序
直接插入排序、折半插入排序、Shell排序
归并排序
桶式排序
基数排序
它们之间的关系:
1.2算法的5大特征
说明:满足确定性的算法也称为:确定性算法。现在人们也关注更广泛的概念,例如 考虑各种非确定性的算法,如并行算法、概率算法等。另外,人们也关注并不要求终 止的计算描述,这种描述有时被称为过程(procedure)。
1.3 冒泡排序
介绍: 冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元 素,如果他们的顺序错误就把他们交换过来。
//冒泡排序
/*
* 排序思想:
*比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
*对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步 做完后,最后的元素会是最大的数。
*针对所有的元素重复以上的步骤,除了最后一个。
*持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要 比较为止。
*/
public static void bubblesort(int [] arr) {
//外层循环控制n的次数
for (int i = arr.length - 1; i > 0; i--) {
//内层循环控制当前所在位置
for (int j = 0; j < i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j,j+1);
}
}
}
}
1.4 快速排序
介绍: 快速排序通常明显比同为O(nlogn)的其他算法更快,因此常被采用,而且快 排采用了分治法的思想,所以在很多笔试面试中能经常看到快排的影子。可 见掌握快排的重要性。
快速排序(Quick Sort)由图灵奖获得者Tony Hoare发明,被列为20世纪十 大算法之一,是迄今为止所有内排序算法中速度最快的一种。冒泡排序的升 级版,交换排序的一种。快速排序的时间复杂度为O(nlog(n))。
//快速排序
/*排序思想:
* 从数列中挑出一个元素,称为"基准"(pivot),
* 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准 值大的摆在基准的后面(相同的数可以到任一边)。
* 在这个分区结束之后, 该基准就处于数列的中间位置。这个称为分区(partition)操作。
*递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数 列排序。
*递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好 了。
*虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代 (iteration)中,它至少会把一个元素摆到它最后的位置去。
*/
public static void QuickSort(int [] arr,int left,int right) {
if(left>right) { //对于传入参数的判断,不能交叉越界
return;
}
int base = arr[left]; //基准元素
int l =left;
int r =right;
while(l!= r) { //只知道循环条件,不知道循环具体情况
while(arr[r]>base&&l<r) {
r--;
}
//从左往右找
while(arr[r]<base&&l<r) {
l++;
}
//交换left与right位置
if(l<=r) {
swap(arr, l,r);
}
//base回归中间位置 第一次排序结束
arr[left]=arr[l];
arr[l]= base;
//递归,继续向基准的左右两边执行和上面同样的操作
QuickSort(arr, left, l-1);
QuickSort(arr, l+1, right);
}
}
1.5 选择排序
选择排序是一种简单直观的排序算法,工作原理为:在未排序的序列中找出最小(大)元素与第一个位置的元素交换位置
注意选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。
然后在剩下的元素中再找最小(大)元素与第二个元素的位置交换,依此类推,直到所有元素排序排序完成。根据上述描述,一共进行n-1趟比较后,就能完成整个排队过程。我们可以知道,第k趟比较需要进行的数组元素的两两比较的次数为n-k次,所以共需要的比较次数为n*(n-1) / 2,因此选择排序算法的时间复杂度与冒泡排序一样,也为O(n^2)。
//选择排序
/*排序思想
* 初始状态:序列为无序状态。
*第1次排序:从n个元素中找出最小(大)元素与第1个记录交换
*第2次排序:从n-1个元素中找出最小(大)元素与第2个记录交换
*第i次排序:从n-i+1个元素中找出最小(大)元素与第i个记录交换
*以此类推直到排序完成
*/
public static void SelectSort(int arr []) {
//控制比较次数
for (int i = 0; i < arr.length; i++) {
//记录最小元素min所在位置,假设开始位置为最小元素
int min = i;
//控制元素之间的比较
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
min = j;
}
else {break;}
}
// 进行位置的交换
if (min != i) {
swap(arr, i, min);
}
}
}
1.6 插入排序(InsertSort)
插入排序是一种简单直观的排序算法,工作原理为构建有序序列,对于未排序元素,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间,直到排序完成,如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。理解了插入排序的思想后,我们便能够得到它的时间复杂度。对于n个元素,一共需要进行n-1轮比较,而第k轮比较需要进行k次数组元素的两两比较,因此共需要进行的比较次数为:1 + 2 + … + (n-1),所以插入排序的时间复杂度同冒泡排序一样,也为O(n^2)。
//插入排序
/* 排序思想:
* 从第一个元素开始,该元素可认为已排序。
*取出下一个元素,在排序好的元素序列中从后往前扫描
*如果元素(已排序)大于新元素,将该元素移到下一位置
*重复3.直到找到已排序的元素小于或等于新元素的位置
*将新元素插入该位置后
*重复2-5直到排序完成
*/
public static void InsertSort(int [] arr) {
for (int i = 0; i < arr.length-1; i++) {
for (int j = i+1; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
swap(arr, j, j-1);
}else {
break;
}
}
}
}
附上整体的程序:
import java.util.Scanner;
/**
* 使用最多的几种内部排序算法
* 冒泡排序
*快速排序
*选择排序
*插入排序
*/
public class Sort {
public static void main(String[] args) {
int[] data = { 12,6,9,23,51,34,52};
bubblesort(data);
System.out.println("冒泡排序");
bianli(data);
System.out.println("---------------");
QuickSort(data, 0, data.length-1);
System.out.println("快速排序");
bianli(data);
System.out.println("---------------");
SelectSort(data);
System.out.println("选择排序");
bianli(data);
System.out.println("---------------");
InsertSort(data);
System.out.println("直接插入排序");
bianli(data);
};
//冒泡排序
/*
* 排序思想:
*比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
*对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步 做完后,最后的元素会是最大的数。
*针对所有的元素重复以上的步骤,除了最后一个。
*持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要 比较为止。
*/
public static void bubblesort(int [] arr) {
//外层循环控制n的次数
for (int i = arr.length - 1; i > 0; i--) {
//内层循环控制当前所在位置
for (int j = 0; j < i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j+1);
}
}
}
}
//遍历数组输出
public static void bianli(int [] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
//交换数组中两个元素的位置
public static void swap(int [] arr,int a,int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
//快速排序
/*排序思想:
* 从数列中挑出一个元素,称为"基准"(pivot),
* 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准 值大的摆在基准的后面(相同的数可以到任一边)。
* 在这个分区结束之后, 该基准就处于数列的中间位置。这个称为分区(partition)操作。
*递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数 列排序。
*递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好 了。
*虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代 (iteration)中,它至少会把一个元素摆到它最后的位置去。
*/
public static void QuickSort(int [] arr,int left,int right) {
if(left>right) { //对于传入参数的判断,不能交叉越界
return;
}
int base = arr[left]; //基准元素
int l =left;
int r =right;
while(l!= r) { //只知道循环条件,不知道循环具体情况
while(arr[r]>base&&l<r) {
r--;
}
//从左往右找
while(arr[r]<base&&l<r) {
l++;
}
//交换left与right位置
if(l<=r) {
swap(arr, l, r);
}
//base回归中间位置 第一次排序结束
arr[left]=arr[l];
arr[l]= base;
//递归,继续向基准的左右两边执行和上面同样的操作
QuickSort(arr, left, l-1);
QuickSort(arr, l+1, right);
}
}
//选择排序
/*排序思想
* 初始状态:序列为无序状态。
*第1次排序:从n个元素中找出最小(大)元素与第1个记录交换
*第2次排序:从n-1个元素中找出最小(大)元素与第2个记录交换
*第i次排序:从n-i+1个元素中找出最小(大)元素与第i个记录交换
*以此类推直到排序完成
*/
public static void SelectSort(int arr []) {
//控制比较次数
for (int i = 0; i < arr.length; i++) {
//记录最小元素min所在位置,假设开始位置为最小元素
int min = i;
//控制元素之间的比较
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
min = j;
}
else {break;}
}
// 进行位置的交换
if (min != i) {
swap(arr, i, min);
}
}
}
//插入排序
/* 排序思想:
* 从第一个元素开始,该元素可认为已排序。
*取出下一个元素,在排序好的元素序列中从后往前扫描
*如果元素(已排序)大于新元素,将该元素移到下一位置
*重复3.直到找到已排序的元素小于或等于新元素的位置
*将新元素插入该位置后
*重复2-5直到排序完成
*/
public static void InsertSort(int [] arr) {
for (int i = 0; i < arr.length-1; i++) {
for (int j = i+1; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
swap(arr, j, j-1);
}else {
break;
}
}
}
}
}
输出结果:
最后,排序算法性能对比
1.从平均时间而言:快速排序最佳。但在最坏情况下时间性能不如堆排序和归 并排序。
2.从算法简单性看:由于直接选择排序、直接插入排序和冒泡排序的算法比较 简单,将其认为是简单算法。对于Shell排序、堆排序、快速排序和归并排序 算法,其算法比较复杂,认为是复杂排序。
3.从稳定性看:直接插入排序、冒泡排序和归并排序时稳定的;而直接选择排 序、快速排序、 Shell排序和堆排序是不稳定排序
4.从待排序的记录数n的大小看,n较小时,宜采用简单排序;而n较大时宜采 用改进排序。
1.8 排序算法的选择
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插入,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插入、冒泡或随机的快速排 序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或 归并排序