这里给大家两个算法可视化网站自己在学习时可以更加直观的理解
VisuAlgo
algorithm-visualizer
1.冒泡排序
冒泡排序是稳定的排序算法,最容易实现的排序, 最坏的情况是每次都需要交换, 共需 遍历 并交换将近n²/2次, 时间 复杂度为O(n²). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有 缓存 的temp变量需要内存 空间 , 因此空间复杂度为常量O(1)。
假设有一个数组[5,4,3,2,1],这个例子举的比较极端点
第一轮:
54321-45321-43521-43251-43215,经过第一轮一一比较找到了最大值
第二轮:
43215-34215-32415-32145 ,第二轮找到第二大数
第三轮:
32145-23145-21345
第四轮:
21345-12345
public static int[] sort(int[] arr){
int length = arr.length;
int temp;
boolean flag = false;
if (arr == null || length==0){
return null;
}
for (int i=0; i<length-1; i++ ){
for (int j=0; j<length-1-i; j++){
if (arr[j] > arr[j+1]){
flag = true;
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
if (!flag){
break;
}else {
flag = false;
}
}
return arr;
}
2. 选择排序
在未排序序列中找到最小(大)元素,存放到未排序序列的起始位置。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
第一轮:24531-14532, 找到最小数与第一位交换
第二轮:14532-12534, 找到最小数与第二位交换
第三轮:12534-12354
第四轮:12354-12345
public static int[] chooseSort(int[] arr){
int length = arr.length;
for(int i=0; i<length-1; i++){
int minIndex = i;
int min = arr[i];
for (int j=i+1; j<length; j++){
if (min> arr[j]){
minIndex = j;
min = arr[j];
}
}
if (minIndex != i){
arr[minIndex] = arr[i];
arr[i] = min;
}
}
return arr;
}
3.插入排序
直接插入排序的基本思想是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过为止。
第一轮:24531,初始默认第一个元素2已经排序,进行前后切割分为有序表和无序表。2|4531。4需要与已经排好序的序列从后往前进行比对。4大于2则直接插入24|531。
第二轮:24|531 ,5与4进行比较5大于4.则不需要移动直接插入。245|31
第三轮:245|31 ,3小于5则需要将5向后移动一位将3插入,2435|1。重复进行知道3的前一位小于3。2345|1。
第四轮:2345|1 , 重复上面步骤即可。
public static int[] insertSort(int[] arr){
for(int i=1; i<arr.length; i++ ){
int insertVal = arr[i];
int insertIndex = i-1;
while (insertIndex >= 0 && insertVal < arr[insertIndex]){
arr[insertIndex+1] = arr[insertIndex];
insertIndex--;
}
if (insertIndex+1 != i){
arr[insertIndex+1] = insertVal;
}
}
return arr;
}
4.希尔排序
到了希尔排序看代码感觉就有点不好理解了,头疼的很我弄了好久才找到感觉。
继续拿上面的数组进行下分析,先看看排序流程。
[5,4,3,2,1],首先希尔排序会确认一个步长,根据步长进行分组。gap=length/2=2这里有人就会觉得为什么5/2不是2.5。首先因为gap是int类型的所以会进行取整。而这里的取整并不是我们想象中的四舍五入。java会直接舍弃后面的精度即使是2.9取整也是2.
接下来就会按照步长分为3组一一对应。
5-3,4-2,3-1。进行比较移动
第一轮:34521,32541,32145。到这里并没有结束1还会与第一位进行最后一次比较。得到123456其实在这里我们经过第一轮就已经得到了有序数组。但是希尔排序还会继续往下判断知道步长为1.
第二轮:gap/2=1。123456。这个时候因为步长为1就会相邻的进行两两比较。经过次轮比较后即返回排序后的数组。
这里我看到很多博客举例都是用的元素为偶数的数组,经过多次打断点调试我发现当数组元素个数为偶数和奇数时是有一点小区别的。当我们再给一个偶数数组。[6,5,4,3,2,1。这种情况下只会进行两两比较交换后因为4到6之间的步长不满足3并不会再进行比较。第一轮就已经直接结束。额感觉说的有点乱又懒得画图。。。。。
重新对照下面代码再来个例子仔细过一遍好了。[3,2,4,1]
第一轮:3-4,2-1.比较后值3142
第二轮:到这里步长为1就跟插入排序一样了,第一个3可以看为是有序表中的。3|142。13|42->134|2->1234就得到最后的有序数组。其实希尔排序就是普通插入排序的一种优化避免了当类似于[2,3,4,5,1]这种排序时使用普通插入排序的一种性能浪费。
贴一张图片方便以后理解:
public static int[] shellSort(int[] arr){
for (int gap = arr.length / 2;gap > 0; gap = gap/2) {
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
if(arr[j]<arr[j-gap]){
while (j - gap >= 0 && temp < arr[j-gap]){
arr[j] = arr[j-gap];
j -= gap;
}
arr[j] = temp;
}
}
}
return arr;
}
5.快速排序
光文字感觉太干涩了把之前推荐的那个可视化网站拉出来溜溜。动图有点麻烦可以自己点开网站输入数组看一下。把大概思路看一下。
[7,5,3,2,4,1,8,9,6],首先以开始元素7为轴心点,从左开始进行遍历拆分。53241都小于7低位组,89都大于7为高位组。直到遍历到6时发现6小于7需要将6放入低位组中,此时只需要将6放入到高位组的左侧低位组的最右侧,即交换高位组左侧值与6的位置即可。交换过后的数组为[7,5,3,2,4,1,6,9,8]。此时以中轴7为基准已经将数组大小进行了划分。[5,3,2,4,1,6]为小于7的数[9,8]为大于7的数。此时只需要将我们的中轴插入到低位组的最右边即形成以7为分界线左小右大的划分。这里只需要交换中轴和低位组中最右边的值即可。即交换7与6的位置,结果就为[6,5,3,2,4,1,7,9,8]。到这里就推断出了我们图二的结果完成第一轮的循环。接下来只需要再以新的中轴6拆分交换不断循环直到高低位相遇循环结束。
public static int[] quickSort(int arr[], int low, int high) {
if (arr == null || arr.length <= 0) {
return null;
}
if (low >= high) {
return null;
}
int left = low;
int right = high;
int key = arr[left];
while (left < right) {
while (left < right && arr[right] >= key) {
right--;
}
while (left < right && arr[left] <= key) {
left++;
}
if (left < right) {
swap(arr, left, right);
}
}
swap(arr, low, left);
quickSort(arr, low, left - 1);
quickSort(arr, left + 1, high);
return arr;
}
public static void swap(int arr[], int low, int high) {
int tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
先到这里。。。
6.归并排序
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
继续拿[7,5,3,2,4,1,8,9,6]来分析一波排序过程。9/2=4,按照4进行拆分[7,5,3,2],[4,1,8,9,6]继续拆分直到分为长度为2的数组。[7,5],[3,2],[4,1],[8,9,6]分别对基数组进行排序后再合并,左侧[5,7],[,2,3],右侧[1,4],[6,8,9]。对左侧进行合并,[5,7],[2,3].2<5把2加入数组[2,x,x,x]。3<5把3加入数组[2,3,x,x]…以此类推此处略去一万次就会分别得到[2,3,5,7],[1,4,6,8,9],再对这两个数组进行合并即可得到最后的结果。去那个可视化网站可以看的很清楚。主要理解思路代码实现方式不止一种。
public static int[] MergeSort(int[] a) {
if (a.length <= 1) {
return a;
}
int num = a.length >> 1;
int[] left = Arrays.copyOfRange(a, 0, num);
int[] right = Arrays.copyOfRange(a, num, a.length);
return mergeTwoArray(MergeSort(left), MergeSort(right));
}
public static int[] mergeTwoArray(int[] a, int[] b) {
int i = 0, j = 0, k = 0;
int[] result = new int[a.length + b.length];
while (i < a.length && j < b.length) {
if (a[i] <= b[j]) {
result[k++] = a[i++];
} else {
result[k++] = b[j++];
}
}
while (i < a.length) {
result[k++] = a[i++];
}
while (j < b.length) {
result[k++] = b[j++];
}
return result;
}
public void mergesort() {
int[] orderedArr = new int[array.length];
for (int i = 2; i < array.length * 2; i *= 2) {
for (int j = 0; j < (array.length + i - 1) / i; j++) {
int left = i * j;
int mid = left + i / 2 >= array.length ? (array.length - 1) : (left + i / 2);
int right = i * (j + 1) - 1 >= array.length ? (array.length - 1) : (i * (j + 1) - 1);
int start = left, l = left, m = mid;
while (l < mid && m <= right) {
if (array[l] < array[m]) {
orderedArr[start++] = array[l++];
} else {
orderedArr[start++] = array[m++];
}
}
while (l < mid)
orderedArr[start++] = array[l++];
while (m <= right)
orderedArr[start++] = array[m++];
}
}
}