冒泡排序
第0个元素开始,依次比较相邻的两个元素,如果前面的元素大于后面的元素,那么将两个元素交换位置。
简单来说就是使用相邻的两个元素依次比较,依次将最大的数放到最后。
这种排序方式就像是将最大的元素一个气泡一样慢慢浮来,所以叫冒泡排序。
同一种排序的计算方式不因编程语言的不同而不同,本篇帖子以java为例:
int[] source = {8, 6, 10, 1, 2, 7, 3};// 要排序的数组
第0轮排序:
- 第一次:8与6比较,8>6,交换位置,得到 6, 8, 10, 1, 2, 7, 3
- 第二次,8与10比较,8<10,不用换位置,得到 6, 8, 10, 1, 2, 7, 3
- 第三次,10与1比较,10>1, 交换位置,得到 6, 8, 1, 10, 2, 7, 3
- 第四次,10与2比较,10>2, 交换位置,得到 6, 8, 1, 2, 10, 7, 3
- 第五次,10与7比较,10>7, 交换位置,得到 6, 8, 1, 2, 7, 10, 3
- 第六次,10与3比较,10>3, 交换位置,得到 6, 8, 1, 2, 7,3,10
第0轮结束后,我们将数组总的最大元素冒泡到了数组的最后面。
第0轮代码实现:
//注意比较次数为source.length - 1次
for (int i = 0; i < source.length - 1; i++) {
//如果前面的元素大于后面的元素
if (source[i] > source[i + 1]) {
//交换位置
int temp = source[i];
source[i] = source[i + 1];
source[i + 1] = temp;
}
}
这里面有个比较次数的问题,试想一下,比较两、2个元素的大小,那么需要比较1次,比较3个元素的大小,需要比较2次。比较N个元素的大小,则需要比较N-1次。
第1轮排序:此时的数据源已经变为6, 8, 1, 2, 7,3,10
在第0轮排序结束以后,我们得到了这样的结果:{6, 8, 1, 2, 7,3,10},跟原来的数组{8, 6, 10, 1, 2, 7, 3}比较,我们发现数组中最大的数字移动到了数组的最右端,那么,在本轮比较中,10这个元素就不需要再参与比较了,所以本轮需要比较的元素从7个变成了6个,那么比较的次数也变成了5次,即第1轮需要比较的次数为n-1-1次。
比较过程如下:
- 第一次:6与8比较,6<8,不交换位置,得到 6, 8, 1, 2, 7,3,10
- 第二次,8与1比较,8>1,交换位置,得到 6, 1, 8, 2, 7,3,10
- 第三次,8与2比较,8>2, 交换位置,得到 6, 1, 2, 8, 7,3,10
- 第四次,8与7比较,8>7, 交换位置,得到 6, 1, 2,7,8,3,10
- 第五次,8与3比较,8>3, 交换位置,得到 6, 1, 2, 7, 3,8,10
第1轮代码实现:
//注意本轮比较次数为source.length - 1-1次
for (int i = 0; i < source.length - 1-1; i++) {
//如果前面的元素大于后面的元素
if (source[i] > source[i + 1]) {
//交换位置
int temp = source[i];
source[i] = source[i + 1];
source[i + 1] = temp;
}
}
第2轮排序推导过程与第0轮和第1轮相似
整个冒泡排序的代码实现:
/**
* 冒泡排序
*/
public void bubbleSort() {
int[] source = {10, 6, 1, 8, 2, 7, 3};
//外层,用于记录和控制第几轮循环
for (int i = 0; i < source.length - 1; i++) {
//内层,用于控制每轮循环的次数
for (int j = 0; j < source.length - 1 - i; j++) {
//如果前面的数大于后面的数,则交换位置
if (source[j] > source[j + 1]) {
int temp = source[j];
source[j] = source[j + 1];
source[j + 1] = temp;
}
}
}
}
选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。
以上摘自百度百科
选择排序分为简单选择排序、树型选择排序和堆排序。
简单选择排序:给定数组arr[],在第n(n<arr.length-1)轮排序过程中,把第arr[n]个元素与之后的所有元素依次比较,如果arr[n]>arr[i],那么则交换位置。
再说简单点:使用每一个元素,与其他元素依次比较,依次将最小的数放到最前面。
同一种排序的计算方式不因编程语言的不同而不同,本篇帖子以java为例:
int[] source = {8, 6, 10, 1, 2, 7, 3};// 要排序的数组
第0轮排序过程如下
- 第一次:8与6比较,8>6,交换位置,得到 6, 8, 10, 1, 2, 7, 3
- 第二次,6与10比较,6<10,不用换位置,得到 6, 8, 10, 1, 2, 7, 3
- 第三次,6与1比较,6>1, 交换位置,得到 1, 8, 10, 6, 2, 7, 3
- 第四次,1与2比较,1<2, 不交换位置,得到1, 8, 10, 6, 2, 7, 3
- 第五次,1与7比较,1<2, 不交换位置,得到1, 8, 10, 6, 2, 7, 3
- 第六次,1与3比较,1<2, 不交换位置,得到1, 8, 10, 6, 2, 7, 3
第0轮结束后,我们将数组总的最小元素 放在了数组的最开始位置。
第0轮代码实现:
//这是第0轮比较的过程,
int count =0;//记录第几轮
// 注意i的初始值,初始值加1是因为自己没有必要与自己比较
for (int i = count+1; i < source.length; i++) {
//始终拿第0个(即第count)元素与之后的元素进行比较
// 如果第0个元素大于之后的元素,则交换位置
if (source[0]>source[i]) {
int temp = source[i];
source[i]=source[0];
source[0]=temp;
}
}
第1轮排序:此时的数据源已经变为1, 8, 10, 6, 2, 7, 3
在第0轮排序结束以后,我们得到了这样的结果:{1, 8, 10, 6, 2, 7, 3},跟原来的数组{8, 6, 10, 1, 2, 7, 3}比较,我们发现数组中最小的数字移动到了数组的最左端,那么,在本轮比较中,1这个元素即arr[0]这个元素就不需要再参与比较了,所以本轮开始比较的数为8,即数组中的第1个元素,也就是说第n轮中开始比较元素的启始位置为arr[n]。
第1轮排序过程如下:
- 第一次:8与10比较,8<10,不用交换位置,得到 1, 8, 10, 6, 2, 7, 3
- 第二次,8与6比较,8>6, 交换位置,得到 1, 6, 10, 8, 2, 7, 3
- 第三次,6与2比较,6>2, 交换位置,得到 1, 2, 10, 8, 6, 7, 3
- 第四次,2与7比较,2<7, 不交换位置,得到1, 2, 10, 8, 6, 7, 3
- 第五次,2与3比较,2<3, 不交换位置,得到1, 2, 10, 8, 6, 7, 3
第1轮的代码实现:
//这是第1轮比较的过程,
int count =1;//记录第几轮
for (int i = count+1; i < source.length; i++) {
//始终拿第1个(即第count)元素与之后的元素进行比较
// 如果第1个元素大于之后的元素,则交换位置
if (source[1]>source[i]) {
int temp = source[i];
source[i]=source[count];
source[1]=temp;
}
}
比较第0轮与第1轮过程发现,这两次比较只是比较的起始位置不同
第2轮排序推导过程与第0轮和第1轮相似
整个简单排序的代码实现过程如下:
/**
* 简单选择排序
*/
public void selectSort() {
int[] source = {8, 6, 10, 1, 2, 7, 3};
//外层,用于控制第几轮循环
for (int i = 0; i < source.length - 1; i++) {
// 内存,用于控制每轮循环次数
for (int j = i + 1; j < source.length; j++) {
//如果第i个数大于后面的数,则交换位置
if (source[i] > source[j]) {
int temp = source[i];
source[i] = source[j];
source[j] = temp;
}
}
}
}
快速排序
快速排序,听这个名字就能想到它排序速度快,它是一种原地排序。其基本思想是随机找出一个数(通常就拿数组第一个数据就行),把它插入一个位置,使得它左边的数都比它小,它右边的数据都比它大,这样就将一个数组分成了两个子数组,然后再按照同样的方法把子数组再分成更小的子数组,直到不能分解为止。 它也是分治思想的一个经典实验(归并排序也是)
快速排序算法过程
下面通过一个例子介绍快速排序算法的思想,假设要对数组a[10]={6,1,2,7,9,3,4,5,10,8}进行排序,首先要在数组中选择一个数作为基准值,这个数可以随意选择,在这里,我们选择数组的第一个元素a[0]=6作为基准值,接下来,我们需要把数组中小于6的数放在左边,大于6的数放在右边,怎么实现呢?
我们设置两个“哨兵”,记为“哨兵i”和“哨兵j”,他们分别指向数组的第一个元素和最后一个元素,即i=0,j=9。首先哨兵j开始出动,哨兵j一步一步地向左挪动(即j–-),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。
即将开始查询
最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。此时就需要交换i和j指向的元素的值。
哨兵分别停在5和7上面,并且进行交换
交换之后的数组变为a[10]={6,1,2,5,9,3,4,7,10,8}:
交换完成
第一次交换至此结束。接下来,由于哨兵i和哨兵j还没有相遇,于是哨兵j继续向前,发现比6小的4之后停下;哨兵i继续向前,发现比6大的9之后停下,两者再进行交换。交换之后的数组变为a[10]={6,1,2,5,4,3,9,7,10,8}。
第二次的查询交换
第二次交换至此结束。接下来,哨兵j继续向前,发小比6小的3停下来;哨兵i继续向前,发现i==j了!!!于是,这一轮的探测就要结束了,此时交换a[i]与基准的值,数组a就以6为分界线,分成了小于6和大于6的左右两部分:a[10]={3,1,2,5,4,6,9,7,10,8}。
第一轮查询交换结束
以上查询交换过程摘抄自
https://blog.youkuaiyun.com/sinat_20177327/article/details/76560079
感谢作者的精心演练
快速排序其实是建立在冒泡排序之上的,可以理解为冒泡排序的优化。
冒泡排序的特点是每次都比较相邻的两个数字,每次位置互换,也只能换一个跨度。 快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。
快速排序代码
/**
* 快速排序算法
*/
public void quickSort(int[] list, int left, int right) {
if (left < right) {
// 分割数组,找到分割点
int point = partition(list, left, right);
// 递归调用,对左子数组进行快速排序
quickSort(list, left, point - 1);
// 递归调用,对右子数组进行快速排序
quickSort(list, point + 1, right);
}
}
/**
* 分割数组,找到分割点
*/
private int partition(int[] list, int left, int right) {
// 用数组的第一个元素作为基准数
int first = list[left];
while (left < right) {
while (left < right && list[right] >= first) {
right--;
}
// 交换
swap(list, left, right);
while (left < right && list[left] <= first) {
left++;
}
// 交换
swap(list, left, right);
}
// 返回分割点所在的位置
return left;
}
/**
* 交换数组中两个位置的元素
*/
private void swap(int[] list, int left, int right) {
int temp;
if (list != null && list.length > 0) {
temp = list[left];
list[left] = list[right];
list[right] = temp;
}
}
快速排序思考:
在上文中的查询过程,为什么每次的查询都从哨兵j开始?
二分法查找的前提是数组必须排序!!!
二分法查找的前提是数组必须排序!!!
二分法查找的前提是数组必须排序!!!
二分法查找:依次将所查的数据与中心数据进行对比,根据大小调整数据边界。
具体来讲,如果所查数据小于中间的数据,那么说明所查数据在中间数据的左侧,那么将查询范围缩小到开始与中心数据之间,如果所查数据大于中心数据,那么说明所查数据在中心数据右侧,那么将查询范围缩小到中心数据与结尾数据之间。重复此过程,直到找到该数据为止。
二分法查找的代码实现:
/**
* 二分法查找
* @return
*/
private void twoPointQuery() {
int[] source = {1, 2, 3, 6, 7, 8, 10};
//查找6的索引
int num = 12;//要查找的数字
int start = 0;//查找的起始角标
int end = source.length - 1;//查找的结尾角标
int mid = (start + end) / 2;//查找的中间角标
while (num != source[mid]) {
if (source[mid] > num) {
end = mid - 1;
} else if (source[mid] < num) {
start = mid + 1;
}
//防止死循环
if (start>end){
mid=-1;// -1代表没有这个数字
break;
}
mid = (start + end) / 2;
}
// 循环结束的时候,mid的值即为要查找的角标值
Log.d("======", "要查找的num的角标为: "+mid);
}