一.冒泡排序
1.原理:
每次比较两个相邻的元素,将较大的元素交换至右端。
2.思路:
(1).比较相邻的元素。
(2).如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。
(3).在这一点,最后的元素应该会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。
(4).持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
3.排序动态图:
4.代码演示:
public class Main {
public static void bubble_sort(int[] array){
for(int i=0;i<array.length-1;i++){
for(int j=0;j<array.length-1-i;j++){
if(array[j]>array[j+1]){
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
}
}
public static void main(String[] args) {
// write your code here
int[] arr=new int[]{3,4,2,14,6};
bubble_sort(arr);
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
二.选择排序
1.原理:
首先在未排序的数列中找到最小(or最大)元素,然后将其存放到数列的起始位置;接着,再从剩余未排序的元素中继续寻找最小(or最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
2.思路:
(1).选择排序法的第一层循环从起始元素开始选到倒数第二个元素,主要是在每次进入的第二层循环之前,将外层循环的下标赋值给临时变量。
(2).接下来的第二层循环中,如果发现有比这个最小位置处的元素更小的元素,则将那个更小的元素的下标赋给临时变量。
(3).最后,在二层循环退出后,如果临时变量改变,则说明,有比当前外层循环位置更小的元素,需要将这两个元素交换。
3.排序动态图:
4.代码演示:
public class Main {
public static void selection_sort(int[] array){
if(array==null || array.length<=1){
return;
}
for(int i=0;i<array.length-1;i++){
int min=i;
for(int j=i+1;j<array.length;j++){
if(array[min]>array[j]){
min=j;
}
}
int temp=array[min];
array[min]=array[i];
array[i]=temp;
}
}
public static void main(String[] args) {
// write your code here
int[] arr=new int[]{3,4,2,14,6};
selection_sort(arr);
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
三.插入排序
1.原理:
一般也被称为直接插入排序,插入排序始终在列表的较低位置维护一个排序的子列表,遇到新的项将它插入到原来的子列表,使得排序的子列表称为一个较大的项
2.思路:
(1).将数组的第一个元素代表为已排序序列,用下一个元素往已排序序列进行插入。
(2).插入到相应位置,循环之前的步骤,直到将所有的元素都插入到已排序序列中
3.排序动态图:
4.代码演示:
public class Main {
public static void insertion_sort(int[] array){
if(array==null || array.length<=1){
return;
}
for(int i=1;i<array.length;i++){
for(int j=i;j>0;j--){
if(array[j-1]>array[j]){
int temp=array[j];
array[j]=array[j-1];
array[j-1]=temp;
}
}
}
}
public static void main(String[] args) {
// write your code here
int[] arr=new int[]{3,4,2,14,6};
insertion_sort(arr);
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
四.希尔排序
1.原理:
希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
2.思路:
(1).选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1。
(2).按增量序列个数k,对序列进行k 趟排序。
(3).每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。
(4).仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
3.排序动态图:
4.代码演示:
public class Main {
public static void shell_sort(int[] array){
if(array==null || array.length<=1){
return;
}
for(int gray=array.length/2;gray>0;gray/=2){
for(int i=gray;i<array.length;i++){
int value=array[i];
int j;
for(j=i-gray;j>=0&&array[j]>value;j-=gray){
array[j+gray]=array[j];
}
array[j+gray]=value;
}
}
}
public static void main(String[] args) {
// write your code here
int[] arr=new int[]{3,4,2,14,6};
shell_sort(arr);
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
五.归并排序
1.原理:
归并排序是一种递归算法,不断将列表拆分为一半,如果列表为空或有一个项,则按定义进行排序。如果列表有多个项,我们分割列表,并递归调用两个半部分的合并排序。一旦对两半排序完成,获取两个较小的排序列表并将它们组合成单个排序的新列表的过程
2.思路:
(1).初始序列是n个记录,则看成n个有序元素,每个序列的长度是1。
(2).然后两两归并,得到n/2个长度为2个或者1的有序序列,然后再两两归并。
(3).直到得到一个长度为n的有序序列为止,这种方法称为2-路归并排序。
3.排序动态图:
4.代码演示:
public class Main {
public static void merge(int[] arr,int low,int high){
int mid=(high+low)/2;
if(low<high){
merge(arr,low,mid);
merge(arr,mid+1,high);
merge_sort(arr,low,mid,high);
}
}
public static void merge_sort(int[] arr,int low,int mid,int high) {
int[] tempArr = new int[arr.length];
int i = low, j = mid + 1;
int index = 0;
while (i <= mid && j <= high) {
if (arr[i] < arr[j]) {
tempArr[index] = arr[i];
i++;
} else {
tempArr[index] = arr[j];
j++;
}
index++;
}
while (i <= mid) {
tempArr[index++] = arr[i++];
}
while (j <= high) {
tempArr[index++] = arr[j++];
}
for (int k = 0; k < index; k++) {
arr[k + low] = tempArr[k];
}
}
public static void main(String[] args) {
// write your code here
int[] arra=new int[]{3,4,2,14,6};
merge(arra,0,arra.length-1);
for(int b=0;b<arra.length;b++){
System.out.println(arra[b]);
}
}
}
六.快速排序
1.原理:
先从数列中取出一个数作为基准数。分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。再对左右区间重复第二步,直到各区间只有一个数。
2.思路:
(1).首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2).将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
(3).然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4).重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
3.排序动态图:
4.代码演示:
public class Main {
public static void quicksort(int[] arr,int low,int high){
int i=low,j=high,temp,res;
if(low>high){
return;
}
temp=arr[low];
while (i<j){
while (temp<=arr[j] && i<j){
j--;
}
while (temp>=arr[i] && i<j){
i++;
}
if (i<j){
res=arr[j];
arr[j]=arr[i];
arr[i]=res;
}
}
arr[low]=arr[i];
arr[i]=temp;
quicksort(arr,low,j-1);
quicksort(arr,j+1,high);
}
public static void main(String[] args) {
// write your code here
int[] arra=new int[]{3,4,2,14,6};
quicksort(arra,0,arra.length-1);
for(int b=0;b<arra.length;b++){
System.out.println(arra[b]);
}
}
}
七.堆排序
1.原理:
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
2.思路:
(1).将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区。
(2).将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n],由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆。
(3).然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn),不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
3.排序动态图:
4.代码演示:
public class Main {
private static void heapsort(int[] arr){
for(int i=(arr.length-1)/2;i>=0;i--){
adjustHead(arr,i,arr.length);
}
for(int i=arr.length-1;i>0;i--){
int temp=arr[i];
arr[i]=arr[0];
arr[0]=temp;
adjustHead(arr,0,i);
}
}
private static void adjustHead(int[] arr,int parent,int length){
int temp=arr[parent];
int lchild=2*parent+1;
while (lchild<length){
int rchild=lchild+1;
if(rchild<length && arr[lchild]<arr[rchild]){
lchild++;
}
if (temp>=arr[lchild]){
break;
}
arr[parent]=arr[lchild];
parent=lchild;
lchild=2*lchild+1;
}
arr[parent]=temp;
}
public static void main(String[] args) {
// write your code here
int[] arra=new int[]{3,4,2,14,6};
heapsort(arra);
for(int b=0;b<arra.length;b++){
System.out.println(arra[b]);
}
}
}
八.计数排序
1.原理:
计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),然后进行分配、收集处理:
分配:扫描一遍原始数组,以当前值-minValue作为下标,将该下标的计数器增1。
收集:扫描一遍计数器数组,按顺序把值收集起来。
2.思路:
(1).先找出待排序的数组中最大和最小的元素。
(2).统计数组中每个值为i的元素出现的次数,存入数组C的第i项,对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加。
(3).反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
3.排序动态图:
4.代码演示:
import java.util.Arrays;
public class Main {
public static void countsort(int[] arr){
if (arr.length==0){
return;
}
int res,min=arr[0],max=arr[0];
for (int i=1;i<arr.length;i++){
if (arr[i]>max) {
max=arr[i];
}
if (arr[i]<min){
min=arr[i];
}
}
res=0-min;
int[] arra = new int[max - min + 1];
Arrays.fill(arra, 0);
for (int i = 0; i < arr.length; i++) {
arra[arr[i] + res]++;
}
int index = 0, i = 0;
while (index < arr.length) {
if (arra[i] != 0) {
arr[index] = i - res;
arra[i]--;
index++;
} else
i++;
}
}
public static void main(String[] args) {
// write your code here
int[] arra=new int[]{3,4,2,14,6};
countsort(arra);
for(int b=0;b<arra.length;b++){
System.out.println(arra[b]);
}
}
}
九.桶排序
1.原理:
桶排序是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。
2.思路:
(1).找出待排序数组中的最大值max、最小值min。
(2).我们使用 动态数组ArrayList 作为桶,桶里放的元素也用 ArrayList 存储。桶的数量为(max-min)/arr.length+1。
(3).遍历数组 arr,计算每个元素 arr[i] 放的桶。每个桶各自排序,遍历桶数组,把排序好的元素放进输出数组。
3.排序动态图:
4.代码演示:
import java.util.ArrayList;
import java.util.Collections;
public class Main {
public static void bucketsort(int[] arr){
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for(int i = 0; i < arr.length; i++){
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
//桶数
int bucketNum = (max - min) / arr.length + 1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
for(int i = 0; i < bucketNum; i++){
bucketArr.add(new ArrayList<Integer>());
}
//将每个元素放入桶
for(int i = 0; i < arr.length; i++){
int num = (arr[i] - min) / (arr.length);
bucketArr.get(num).add(arr[i]);
}
//对每个桶进行排序
for(int i = 0; i < bucketArr.size(); i++){
Collections.sort(bucketArr.get(i));
}
System.out.println(bucketArr.toString());
}
public static void main(String[] args) {
// write your code here
int[] arra=new int[]{3,4,2,14,6};
bucketsort(arra);
}
}
十.基数排序
1.原理:
将所有待比较数值统一为同样的数字长度,数字较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
2.思路:
(1). 求最大位数。由于基数排序是按位排序,所以先要确定最大位数,才能知道排序的趟数。求最大位数的方法是求最大元素的最高位,用循环做比较简单。
(2). 分配:由于每位的取值范围是0-9,因此需要十个容器来装,我们一般用十个队列即可,这十个队列标号为0-9。对于每一趟,我们取每一个元素在该位的数值依次入队。
(3). 收集:在一趟排序完成后,我们按顺序从0-9队列中依次出队收集元素。
(4). 继续进行分配和收集,直到最大位数排序完成。
3.排序动态图:
4.代码演示:
import java.util.Arrays;
public class Main {
public static void radixsort(int[] arr, int radix, int d) {
// 缓存数组
int[] temp = new int[arr.length];
// buckets用于记录待排序元素的信息
// buckets数组定义了max-min个桶
int[] buckets = new int[radix];
for (int i = 0, rate = 1; i < d; i++) {
// 重置count数组,开始统计下一个关键字
//Arrays.fill( a1, value )
//a1是一个数组变量,value是一个a1中元素数据类型的值,作用:填充a1数组中的每个元素都是value
Arrays.fill(buckets, 0);
// 将data中的元素完全复制到tmp数组中
System.arraycopy(arr, 0, temp, 0, arr.length);
// 计算每个待排序数据的子关键字
for (int j = 0; j < arr.length; j++) {
int subKey = (temp[j] / rate) % radix;
buckets[subKey]++;
}
for (int j = 1; j < radix; j++) {
buckets[j] = buckets[j] + buckets[j - 1];
}
// 按子关键字对指定的数据进行排序
for (int m = arr.length - 1; m >= 0; m--) {
int subKey = (temp[m] / rate) % radix;
arr[--buckets[subKey]] = temp[m];
}
rate *= radix;
}
}
public static void main(String[] args) {
int[] arr= {11,55,33,77,44,66,22,99};
radixsort(arr,10,3);
System.out.println(Arrays.toString(arr));
}
}
十一.排序算法总结:
1.术语说明
术语 | 描述 |
---|---|
稳定 | x本来是在y的前面,但是x=y,排序之后x仍然在y的前面 |
不稳定 | x本来是在y的前面,但是x=y,排序之后x可能会出现在y的后面 |
内排序 | 所有的排序操作都是在内存中完成 |
外排序 | 由于数据太大,因此需要把数据放在磁盘中,而排序则需要通过磁盘和内存的数据传输才能进行 |
时间复杂度 | 一个算法执行所耗费的时间 |
空间复杂度 | 运行完一个程序所需要的内存大小 |
2.算法知识点归纳
排序算法 | 最好情况 | 最坏情况 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(n(logn)^2) | O(n(logn)^2) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(nlogn) | O(n^2) | O(nlogn) | O(logn) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | 稳定 |
桶排序 | O(n+k) | O(n^2) | O(n+k) | O(n+k) | 稳定 |
基数排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | 稳定 |