分类:
指将需要处理的所有数据都加载到内部存储器中进行排序。
数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。
性能比较
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
俩俩比较,小的换在前,大的换在后面,依次向后循环这个过程
优化
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,在进行)
// // 将前面额冒泡排序算法,封装成一个方法
public static void bubbleSort(int[] arr) {
// 冒泡排序 的时间复杂度 O(n^2), 自己写出
int temp = 0; // 临时变量
boolean flag = false; // 标识变量,表示是否进行过交换
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
// 如果前面的数比后面的数大,则交换
if (arr[j] > arr[j + 1]) {
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
//System.out.println("第" + (i + 1) + "趟排序后的数组");
//System.out.println(Arrays.toString(arr));
if (!flag) { // 在一趟排序中,一次交换都没有发生过
break;
} else {
flag = false; // 重置flag!!!, 进行下次判断
}
}
}
快速排序(Quicksort)是对冒泡排序的一种改进。
基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据
都比另外一部分的所有数据
都要小
,然后再按此方法对这两部分数据分
别进行快速排序
,整个排序过程可以递归进行
,以此达到
整个数据变成有序序列
。
过程
对6 1 2 7 9 3 4 5 10 8 进行排序
首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动
,这一点非常重要。哨兵j
一步一步地向左挪动
(即j–-
),直到找到一个小于6的数停
下来。接下来
哨兵i
再一步一步向右挪动
(即i++
),直到找到一个数大于6的数停
下来。最后哨兵j停在了数字5
面前,哨兵i停在了数字7
面前。
到此,第一次交换结束。接下来开始哨兵j继续向左挪动
(再友情提醒,每次必须是哨兵j先出发
)。他发现了4
(比基准数6要小,满足要求)之后停
了下来。哨兵i
也继续向右挪动
的,他发现了9
(比基准数6要大,满足要求)之后停
了下来。此时再次进行交换
,交换之后的序列如下: 6 1 2 5 4 3 9 7 10 8
快速排序
之所比较快,因为相比冒泡排序,每次交换是跳跃式的
。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序
一样每次只能在相邻的数之间进行交换
,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了
。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的都是O(N2),它的平均时间复杂度为O(NlogN)。
代码
//方法1:便于理解
public static void quickSort2(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;//起始位
j=high;
//temp就是基准位
temp = arr[low];
while (i=arr[i]&&i pivot) {
r -= 1;
}
//如果l >= r说明pivot 的左右两的值,已经按照左边全部是
//小于等于pivot值,右边全部是大于等于pivot值
if( l >= r) {
break;
}
//交换
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//如果交换完后,发现这个arr[l] == pivot值 相等 r--, 前移
if(arr[l] == pivot) {
r -= 1;
}
//如果交换完后,发现这个arr[r] == pivot值 相等 l++, 后移
if(arr[r] == pivot) {
l += 1;
}
}
// 如果 l == r, 必须l++, r--, 否则为出现栈溢出
if (l == r) {
l += 1;
r -= 1;
}
//向左递归
if(left < r) {
quickSort(arr, left, r);
}
//向右递归
if(right > l) {
quickSort(arr, l, right);
}
}
选择排序:每趟从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录序列末尾,直到全部排序结束为止。
举例:对 9 1 2 5 7 4 8 6 3 5进行简单选择排序
通过下面的排序可以发现,是将未排序的部分的最小值取出来,与未排序好的第一个数进行交换
所以排序后为:1 2 3 4 5 5 6 7 8 9
堆是具有以下性质的完全二叉树
堆排序是将堆中的结点按照宽度优先将其进行编号
,将这种逻辑结构映射到数组中
堆排序基本思想:
将4 6 8 5 9进行堆排序
第一步:将给出的无序序列构成一个大顶堆
(一般升序采用大顶堆,降序采用小顶堆
)
1.将无序序列构建为二叉树,按照宽度优先进行构建
2. 从最后一个非叶子结点6开始,从左至右,从下至上进行调整。
3.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
4.交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。无需序列构已经造成了一个大顶堆。
第二步:将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
1.将堆顶元素9和末尾元素4进行交换
2.重新调整结构,使其继续满足堆定义
3.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
4.按照123步骤,继续进行调整,交换,如此反复进行,最终使得整个序列有序
堆排序参考链接
插入排序:每一趟将一个待排序的记录,按照其关键字的大小插入到有序队列的合适位置里,知道全部插入完成
直接插入排序:是一种最简单的插入排序
其基本操作是:将一条记录插入到已排好的有序表中,从而得到一个新的、记录数量增1的有序表
。
步骤:
a[0]自动生成一个有序区
,无序区为:a[1...n-1]
。此时i=1,令i为有序区和无序的标记,即i是无序区的第一个数字
举例说明:对 9 1 2 5 7 4 8 6 3 5进行直接插入排序
通过比较这些步骤可以发现,插入排序是将未排序的部分的第一个数字,与已排序部分的最后一个数字进行比较,如果小于就在已排序部分进行比较,如果大于就直接放在已排序部分的最后一个
希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序
基本思想:该方法实质上是一种分组插入方法
把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
基本步骤:
注:为了方便理解,所以才用组来叙述,其实应该是叫做增量,也被称为希尔增量,那些个数组组其实就是增量序列
顺序查找的基本思想:
查找效率低
。 适用
于线性表的顺序存储结构
和链式存储结构
。public static int sequentialSearch(int[] a, int key) {
for ( int i = 0; i < a.length; i++) {
if (a[i] == key)
return i;
}
return - 1;
}
顺序表查找优化
因为每次循环时都需要对i是否越界,即是否小于等于n作判断。事实上,还可以有更好一点的办法,设置一个哨兵,可以解决不需要每次让i与n作比较。看下面的改进后的顺序查找算法代码。
public static int sequentialSearch2(int[] a, int key) {
int index = a.length - 1;
a[ 0] = key; // 将下标为0的数组元素设置为哨兵
while (a[index] != key) {
index--;
}
return index;
}
折半查找(Binary Search)技术,又称为二分查找。它的前提是线性表中的记录必须是关键码有序(通常从小到大有序)
,线性表必须采用顺序存储
。
折半查找的基本思想是:
取中间记录
作为比较对象
,public static int binarySearch(int[] a, int key) {
int low, mid, high;
low = 0; // 最小下标
high = a.length - 1; // 最大小标
while (low < high) {
mid = (high + low) / 2; // 折半下标
if (key > a[mid]) {
low = mid + 1; // 关键字比 折半值 大,则最小下标 调成 折半下标的下一位
} else if (key < a[mid]) {
high = mid - 1; // 关键字比 折半值 小,则最大下标 调成 折半下标的前一位
} else {
return mid; // 当 key == a[mid] 返回 折半下标
}
}
return -1;
}
最终我们折半算法的时间复杂度为O(logn),它显然远远好于顺序查找的O(n)时间复杂度了。
二分查找特别适用于那种一经建立就很少改动而又经常需要查找的线性表。
又称索引顺序查找,这是顺序查找的一种改进方法,用于在分块有序表中进行查找 。
主表:存储数据的表,长度n;
索引表:将主表分块,每块长s,找出每块中的关键字最大值,并且保存该块中所有数据在主表中的索引
(1)分块:将n个数据元素“按块有序”划分为m块。
每一块中的结点不必有序,但块与块之间必须“按块有序”;即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素。每个块中元素不一定是有序的。
(2)根据查找值,和索引表中关键字(每块中的最大关键字)比较,通过对分查找/顺序查找,找到该值所在的块范围;
(3)在相应块中,找到该值在主表中的位置。
平均查找长度ASL<=O(log2(n/s))+s/2 (先对分查找,或顺序查找)