for(int i = 1; i <= n-1; i++){
int t = i;
for(int j = i+1; j <= n; j++){
if(a[j] < a[t]) t = j;
}
swap(a[t],a[i]);
}
for(int i = 1; i <= n-1; i++){
for(int j = 1; j <= n-1; j++){
if(a[j] > a[j+1]) swap(a[j],a[j+1]);
}
}
改进的冒泡排序
for(int i = 1; i <= n-1; i++){
bool ok = true;
for(int j = 1; j <= n-1; j++){
if(a[j] > a[j+1]){
swap(a[j],a[j+1]);
ok = false;
}
}
if(ok) break;
}
for(int i = 2; i <= n; i++){
int t = a[i];
int j;
//从i的前一位开始扫描
for(j = i-1; j >= 1; j--){
if(a[i] > a[j]) break;
}
//a[j+1]~a[i-1] 后移一位
for(int k = i-1; k >= j+1; k--) a[k+1] = a[k];
a[j+1] = t;
}
for(int i = 1; i <= n; i++){
b[a[i]]++;
}
for(int i = 1; i <= n; i++){
while(b[i] > 0){
cout << i << " ";
b[i]--;
}
}
void quickSort(int a[],int l,int r){
if(l >= r) return;
int i = l,j = r;
int pivot = a[i];
//两指针相遇,即可退出赋值
while(i < j){
//i < j是因为相遇时,保持相遇状态,i,j指针不动,等待赋值结束。
// >=pivot 等号都可以去掉,如果去掉,交换的次数多一点点。
while(a[j] >= pivot && i < j) j--;
a[i] = a[j];
while(a[i] <= pivot && i < j) i++;
a[j] = a[i];
}
a[i] = pivot; //期望位置,ij指针相同的位置
quickSort(a,l,i-1);
quickSort(a,i+1,r);
}
首先解释一下算法的大致思路,pivot = a[l], 备份第一个元素,此时a[l]空闲,a[l] 盛放从右边开始查找的比pivot小的元素a[j],
置换过后,此时a[j]空闲,a[j]盛放从左边开始查找的比pivot大的元素,
以此循环 ,达到交换的目的,直到i,j 指针重合,当i,j指针重合后,内外层循环结束,a[i] = a[j] 是空闲的区域,将pivot拷贝至此,pivot的位置就是最终位置。
下面来解释外层循环while为什么要 i < j 而不是 i<=j:
什么时候会造成 i== j? 内层的两个while循环均可造成 i== j 。
比如,左边a[j] 空闲,现在执行第一个while循环,指针j从右边开始找,j --, 此时 i== j , 执行a[i] = a[j] (无效拷贝),第二个循环就不满足了(因为 i < j 不成立), a[i] == a[j] 是空闲的,到这里就执行结束了,外层循环就不需要执行了,所以外层循环是 i < j
另外一种情况是,执行第一个while循环,j--,找到一个元素a[j] < pivot, 拷贝到左边的空闲元素a[i] 之后,此时a[j]空闲,现在执行第二个循环,从左边执行i++寻找a[i] > pivot拷贝到右边。 i++一直执行都未找到, 此时 i == j, a[i] = a[j] (无效拷贝), 执行到这里,交换就可以结束了,所以外层循环是 i < j。
当然外层循环写成while(i <= j)执行结果也没有影响,内层两个while循环也不会执行,a[i] = a[j] , a[j] = a[i] ,属于无效拷贝。
外层while i < j 写不写等于号要思考清楚啊--!
下面来解释递归前为什么要写if判断条件:
当递归过程中只有一个元素,i、j重合后, l == i == j , i-1 < l , 递归时就要判断条件 if(l < i-1) ,
这只是一个特殊情况,下面看所有的情况
1.当数组第一个元素最小(也是支点),l == i == j == 第一个下标, 也要判断条件if(l < i-1)
2. 当数组第一个元素最大, 循环一次后,i == j == r == 最后一个元素的下标,这时就要判断条件 if(i+1 < r), 否则就要越界
下面来解释内存循环为什么是 a[j] >= pivot 而不是 a[j] > pivot ?
其实,去掉等于号,也不影响算法的执行结果。
当写pivot时,扫描到a[j] == pivot 时 忽略到 此 a[j] 寻找比 pivot 小的元素,因为此时数组有至少两个pivot值相同的元素了,pivot可以不处理。
只要最终pivot左边的元素小于等于pivot,右边的元素大于等于pivot就行了,pivot的位置也是该值得最终位置。
去掉等号,与pivot相等的值也参与交换过程,多了几次交换而已,只要保证一开始的支点元素到最终位置就行,和支点相同的元素到哪边都行,不考虑最方便
内层第二个while循环的 i < j 是为了控制,当第一个while循环到达了i== j,就可以退出循环了。
那么第一个while循环的 i < j 是为了啥呢。
是因为:如果第一元素最小,此时 j -- ,一直找不到比pivot小的元素,防止 j 下标 越界。
那么,第二个while 循环的 i < j 有没有防止越界的功能呢?
如果第一个元素最大,执行第一个while循环后,a[r] 空闲,然后执行第二个while循环 i++, 一直找,直到 i == j == r 不满足a[i] <= pivot 了, 他会自己停下来,所以没有防止越界的功能。
如果pivot选择最后一个元素,就有了防止越界的功能。
i,j 指针最后重叠后,什么时候与a[l] 交换,交换的正确性是要讨论的,其他的方面临界条件与方案一相似
好像和第一个内层while循环有关, 每一个外层循环,就找到一对进行交换。
如果,a[l] 元素最小,j -- , j-- ..... 知道 i == j = a[l] , 所有的循环就终止了,swap(a[l],a[i]) 属于无效交换。
如果,a[l] 元素最大,a[j] = a[r-1] , i++ , i++........直到 i == j == a[r-1] , swap(a[i],a[j]) 属于无效交换。
void quickSort(int a[],int l ,int r){
if(l >= r) return;
int i = l,j = r;
int pivot = a[l];
while(i < j){
while(a[j] >= pivot && i < j) j--;
while(a[i] <= pivot && i < j) i++;
if(i < j) swap(a[i],a[j]);
}
swap(a[i],a[l]);
quickSort(a,l,i-1);
quickSort(a,i+1,r);
}
有四种情况,第一种情况,第一个while循环找到比支点小的数,第二个while循环找到比支点大的数,进行交换,这样子没问题。
第二种情况,第一个while循环找到比支点小的数,第二个while循环找不到,i,j指针就重合了,不交换,i下标是不是支点a[l]的最终位置呢?第三种情况第一个while循环找不到比支点小的数,i,j就重合了,这种情况属于支点是最小的数。第四种情况是第一个while循环和第二个while循环都找不到,这种情况也属于第一个while循环找不到的情况
第三种情况属于支点是最大的情况,首先执行j--,右边第一个数就是比支点小的数。然后i++,i++......直到i==j,执行i++的过程中,略过的数据都是比支点小的数,支点与最后一个数交换就是最终位置。其他的数都是比支点小啊。
考虑最终位置,就要考虑i,j相等的触发情况。
第一个while循环触发是因为,当前查找区间内所有的数都比支点大,找不到比支点小的数,j--,j--直到 i==j,结束所有循环,此时区间最左边的位置就是pivot的最终位置,因为他右边的值都比支点大。第二个while循环触发结束循环,是因为当前查找区间的所有值都比支点小,i++找不到比支点大的值,所以此时查找区间的最右边的位置就是支点的最终位置,因为其他位置都比支点小。
方案一无论哪个内层while循环触发,i==j,都是空闲位置,都是支点的最终位置
void quickSort(int a[],int l, int r){
if(l >= r) return;
int i = l,j = r;
int pivot = a[(l+r)/2];
while(i <= j){
while(a[j] > pivot) j--;
while(a[i] < pivot) i++;
if(i <= j){
swap(a[i],a[j]);
i++;
j--;
}
}
quickSort(a,l,j);
quickSort(a,i,r);
}
为什么内层循环是a[j] > pivot 而不是 a[j] >= pivot ?
是因为pivot是在中间,如果是 >= , 则会一直 j-- , j -- ......会造成越界。
为什么外层循环是 i < j , 而不是 i <= j ?
因为递归条件是 , [l , r] , [i, r] , i和j 必须见面后,背靠背走一步。当 i== j 时,再执行一次循环。此时 a[i] == a[j] == pivot, 内层的两个while循环不执行,swap(a[i],a[j])属于无效交换。 i++,j-- 分离一步即可。
执行结束后,pivot 左边的一定比pivot小吗,右边的一定比pivot大吗? 执行的结果正确吗?
因为是 a[j] > pivot , 如果有和pivot值相同的元素则不考虑。
有两种情况使i== j ,使条件终止,第一种情况是,第一个while循环,从右边开始找,此时的pivot在最左边, j--, j--......一直找不到比pivot小的元素。直到 i == j ,此时a[i] == a[j] == pivot, 第二个while循环不执行,swap无效交换后, i++,j-- 背靠背走一步相离开
void quickSort(int a[],int l, int r){
if(l >= r) return;
int i = l-1,j = r+1;
int pivot = a[(l+r)>>1];
while(i < j){
do i++;while(a[i] < pivot);
do j--;while(a[j] > pivot);
if(i < j) swap(a[i],a[j]);
}
quickSort(a,l,j);
quickSort(a,j+1,r);
}
我们来讨论边界问题的正确性。使外层循环的终止条件不是 i == j。造成这种情况有两种原因,第一个while循环,或者是第二个while 循环。
递归区间是什么?
每次执行内层的while循环,都是在上次执行的结果上,走一步。
第一个while循环停止的时候是, 找到a[i] >= pivot, 一定会找到。
第二个while循环停止的时候是,找到一个a[j] <= pivot, 一定会找到。
什么时候会停止呢?
当支点最小,且执行第一个while循环后, a[i] == pivot, 然后执行第二个while循环,j -- , j-- ......找不到比 pivot小的数,直到 a[i] == a[j] == pivot。这个时候 i == j
这也是递归区间 “不分叉” 的原因,支点不在最终的位置,不能用此方法求第k大值。
归并排序主要分为两大步 , 归并,排序。
void msort(int a[],int l,int r){
if(l == r) return;
//划分
int mid = (l+r)/2;
msort(a,l,mid);
msort(a,mid+1,r);
//归并
int i = l,j = mid+1,k = l;
while(i <= mid && j <= r){
if(a[i] > a[j]) b[k++] = a[j++];
else b[k++] = a[i++];
}
//处理没有完全归并的一半
while(i <= mid) b[k++] = a[i++];
while(j <= r) b[k++] = a[j++];
//b数组拷贝至原数组a
for(int i = l; i <= r; i++) a[i] = b[i];
}
每个数字递归到最后一层后,每个数字向树的最顶端返回时,从最少的数进行比对逆序数。
从单个数字来看,在返回的过程中,该数字对比的数字越来越多。