(1、冒泡排序 2、选择排序 3、插入排序 4、归并排序 5、快速排序 6、堆排序 7、希尔排序)
1、冒泡排序
原理:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
实现:
以数组{2,1,5,4,3}为例,实现过程如下图所示
代码:
public static void bubble(int [] a){
for(int i=0;i<a.length-1;i++){
for(int j=0;j<a.length-1-i;j++){
if(a[j]>a[j+1]){
int temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
}
}
时间复杂度:若数组为正序,则一轮比较即可完成排序,时间复杂度为O(n)
若数组为逆序,则需要n-1轮起泡,每轮需要n-i-1次比较,时间复杂度为O(n^2)
2、选择排序
原理:在要排序的一组数中选出最小的数和第一个位置的数交换,在剩下的数中选择最下的和第二个位置的数交换,如此循环到最后一个数和倒数第二
个数比较。
实现:
仍以数组{2,1,5,4,3}为例,实现过程如下图所示

代码:
public static void select(int [] a){
for (int i = 0; i < a.length; i++) {
int min = a[i];
int n=i;
for(int j=i+1;j<a.length;j++){
if(a[j]<min){
min = a[j];
n = j;
}
}
a[n] = a[i];
a[i] = min;
}
}
时间复杂度:O(n^2)
3、插入排序
原理:将第i个数与前i-1个数依次比较,如果有比i大的数则将i插入到这个数的前面。
实现:仍以数组{2,1,5,4,3}为例,实现过程如下图所示
代码:
public static void insert(int [] a){
int temp=0 ;
int j=0;
for(int i=0;i<a.length;i++)
{
temp=a[i];
//如果第i个数比前面的数小,就让前面的数向后移
for(j=i;j>0&&temp<a[j-1];j --)
{
a[j]=a[j-1];
}
a[j]=temp;
}
}
时间复杂度:O(n^2)
以上是时间复杂度为O(n^2)的排序
下面介绍时间复杂度为O(nlogn)的排序
4、归并排序
原理:假设序列有n个元素,将此序列看成是n个长度为1的序列,将每两个相邻的序列进行归并操作,形成floor(n/2)个序列,排序后每个序列包含两个元素,将上述序列继续两两归并,形成floor(n/4)个序列排序,继续重复上一步,直至形成一个长度为n的序列。
实现:
以数组{3,5,4,2,1,8,9,7}为例,实现过程如下图所示
代码:
public static void sort(int[] data, int left, int right) {
if (left < right){
//找出中间索引
int center = (left + right) / 2;
//对左边数组进行递归
sort(data, left, center); //递归时先调用的后返回,后调用的先返回,因此形参的值也跟随,根据栈的原则
//对右边数组进行递归
sort(data, center + 1, right); /*当center是0的时候,left=right,因此sort不再执行,继续*/
//合并
merge(data, left, center, right);
}
}
public static void merge(int[] data, int left, int center, int right){
int[] tmpArr = new int[data.length];
int mid = center + 1;
//third记录中间数组的索引
int third = left;
int tmp = left;
while (left <= center && mid <= right){
//从左右两个数组中取出小的放入中间数组,
if (data[left]-data[mid]<= 0){
tmpArr[third++] = data[left++];
} else{
tmpArr[third++] = data[mid++];
}
}
//比较后剩余的其他数字部分依次放入中间数组
while (mid <= right) {
tmpArr[third++] = data[mid++];
}
while (left <= center) {
tmpArr[third++] = data[left++];
}
//将中间数组中的内容复制拷回原数组
//(原left~right范围的内容复制回原数组)
while (tmp <= right){//已经排序好的数组
data[tmp] = tmpArr[tmp++];
}
System.out.println("merge");
}
5、快速排序
原理:
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
实现:以数组{3,5,4,2,1,8,9,7}为例,实现过程如下图所示
代码:
public static void quickSort(int[] data, int start, int end) {
int i = start; //相当于i,左 索引
int j = end; //相当于j, 右 索引
if (i >= j) { // 判断是否到中间了 ,到中间返回
return; //返回
}
//确定指针方向的逻辑变量,也就是从左搜索还是向右搜索
boolean flag=true; //false:左->右 true:右->左
while (i != j) { //如果i==j证明第一趟结束,每趟的标准值仅是一个,例如第一趟被比较值为49,
if (data[i] > data[j]) {
//交换数字 :所有比它小的数据元素一律放到左边,所有比他大的数据元素一律放到右边,
int temp = data[i];
data[i] = data[j];
data[j] = temp;
//只有经过数的交换后,才能把游标的移动位置改变,移动书序是交替改变
//决定下标移动,还是上标移动 ,游标更改 走下一个数据
flag = (flag == true) ? false : true;
// flag=!flag;
}
//将指针向前或者向后移动 ,第一次从左-》右,第二次从右-》左
if(flag) {//true,右---》左
j--;
}else{//false 左---》右
i++;
}
}
i--;
j++;
quickSort(data, start, i);
quickSort(data, j, end);
}
快排时间复杂度最优为O(nlogn),最差为O(n^2)
空间复杂度O(logn)~O(n)
6、堆排序
原理:先将待排序的数列构造成一个大顶堆(每个节点的值都比他的子节点的值大),此时序列的最大值就是堆顶的根节点,将其序列最后的元素交换,然后将剩下的n-1个数继续构造成一个大顶堆,则根节点为倒数第二的数,将其与序列倒数第二个位置的元素交换,如此反复最终得到一个正序序列。
实现:以数组{9,79,46,30,58,49}为例,实现过程如下图所示
代码:
public static void heapSort(int [] data) {
System.out.println("开始排序");
int arrayLength = data.length;
//循环建堆
/*
* 9
* / \
* 79 46
* / \ /
* 30 58 49
*/
//针对完全二叉树,大顶堆,最顶端的值(0号索引)肯定是在没swap之前的最大的值,
//对其在数组里面进行排序交换,大的排在数组后面,虽然最大的在最上面,但是其他的数据并没有排好,因此,需要不停更换顶点,重新建堆,需要循环
for (int i = 0; i < arrayLength - 1 ; i++ ){//5 4 3 2 1
//建堆
builMaxdHeap(data, arrayLength - 1 -i);//-i的原因,就是数组中没经过一次循环,最大的数已经在数组的后面了,就不用在建堆了
//由于是大顶锥方式:锥顶是最大的数,交换堆顶和最后一个元素
System.out.print("交换锥顶前");
System.out.println(Arrays.toString(data));
swap(data, 0 , arrayLength - 1 - i);
System.out.print("交换锥顶后");
System.out.println(Arrays.toString(data));
}
}
private static void builMaxdHeap(int [] data, int lastIndex){//修建大顶堆
//从lastIndex处节点(最后一个节点)的父节点开始
//(lastIndex - 1)/2 为最后一个节点的父节点的索引(除根节点)
//都是从子节点开始判断 k的值分别是 5 3 1 索引号依次判断
/*
* 9
* / \
* 79 46
* / \ /
* 30 58 49
*/
for (int i = (lastIndex - 1) / 2 ; i >= 0 ; i--){//索引号从大的开始
//k保存当前正在判断的节点
int k = i;
//如果当前k节点的子节点存在,
//k * 2 + 1 判断当前节点的子节点
while (k * 2 + 1 <= lastIndex){//看当前要判断k的子节点是否存在。
//k节点的左子节点的索引
int biggerIndex = 2 * k + 1;
//如果biggerIndex小于lastIndex,即biggerIndex + 1
//代表k节点的右子节点存在
if (biggerIndex < lastIndex){//防止溢出
//如果右子节点的值较大,biggerIndex存储的都是最大节点索引
if(data[biggerIndex]-(data[biggerIndex + 1]) < 0){//左面的小于右面的
//biggerIndex总是记录较大子节点的索引,之后再和父亲比
biggerIndex++; //这是较大节点的索引号,也就是右节点的索引号
}
}
//如果k(父节点)节点的值小于其较大子节点(左右都有可能)的值,交换
if(data[k]-(data[biggerIndex]) < 0){
//交换它们
swap( data,k , biggerIndex);
//将biggerIndex赋给k,开始while循环的下一次循环,
//重新保证k节点的值大于其左、右子节点的值。
k = biggerIndex;//大节点的索引号,
}else{//满足的条件刚好是节点的值大于子节点的值
break;//刚好进入下一层循环
}
}
}
}
//交换data数组中i、j两个索引处的元素
private static void swap(int [] data, int i , int j){
int tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
7、希尔排序
原理:
把记录按
步长
gap
分组,对每组记录采用
直接插入排序
方法进行排序。
随着
步长逐渐减小
,所分成的组包含的记录越来越多,当步长的值减小到
1
时,整个数据合成为一组,构成一组有序记录,则完成排序。
实现:
实现:
代码:
public void shellSort(int[] list) {
int gap = list.length / 2;
while (1 <= gap) {
// 把距离为 gap 的元素编为一个组,扫描所有组
for (int i = gap; i < list.length; i++) {
int j = 0;
int temp = list[i];
// 对距离为 gap 的元素组进行排序
for (j = i - gap; j >= 0 && temp < list[j]; j = j - gap) {
list[j + gap] = list[j];
}
list[j + gap] = temp;
}
gap = gap / 2; // 减小增量
}
}
(希尔排序的文字描述、图片、代码均来自-> 希尔排序 )