void rank(int x[], int r[], const int size)
{
for(int i = 1; i < size; ++i)
{
for(int j = 0; j < i; ++j)
{
if(x[j] <= x[i])
++r[i];
else
++r[j];
}
}
}
int main(int argc, const char * argv[])
{
const int size = 10;
int array_toSort[size] = {1,6,4,3,5,7,8,5,4,3};
int array_rank[size] = {0};
rank(array_toSort, array_rank, size);
for(int i = 0; i < size; ++i)
{
cout << array_rank[i] << " ";
}
cout << endl;
return 0;
}
结果:
0 7 3 1 5 8 9 6 4 2
再试一次;
//
// main.cpp
// SortAndOthers
//
// Created by Bert Jiachen Wang on 1/17/21.
//
#include <iostream>
#include <string>
using std::cout; using std::endl;
using std::string;
//void rank(int x[], int r[], const int size)
//{
// //initialize r
// for(int i = 0; i < size; ++i)
// {
// r[i] = 0;
// }
//
// //这叫做比较所有数对
// for(int i = 1; i < size; ++i)
// {
// for(int j = 0; j < i; ++j)
// {
// if(x[j] <= x[i])
// ++r[i];
// else
// ++r[j];
// }
// }
//}
void rank(int x[], int r[], const int size)
{
for(int i = 1; i < size; ++i)
{
for(int j = 0; j < i; ++j)
{
if(x[j] <= x[i])
++r[i];
else
++r[j];
}
}
}
int main(int argc, const char * argv[])
{
const int size = 5;
int array_toSort[size] = {5,4,3,2,2};
int array_rank[size] = {0};
rank(array_toSort, array_rank, size);
for(int i = 0; i < size; ++i)
{
cout << array_rank[i] << " ";
}
cout << endl;
return 0;
}
结果:
4 3 2 0 1
Program ended with exit code: 0
————————————————————
原理和注意事项:
原理出乎意料地不好理解。
为什么左加一下右加一下就得到正确的ranking了呢?
我比较喜欢分别从“比较过程”和从“结果”来想:
注意在比较过程中有一个步骤,是将所有的元素“两两比较”一下。如果一个元素比另外一个元素小,那对它没什么影响,如果一个元素比另一个元素大,它要加上。单独拿出一个元素来看,在“两两比较”的过程中,这个元素要和所有的其它元素比较一次,有多少比它小的,就要加上几次1。
而在结果中,如果把所有数据都排排好,然后给他们写上顺序,正是:每个元素,比它小的元素的个数,和它的排序成正相关。
结果和过程相对应->过程所做的可以得到正确的结果
关键词/关键步骤:
元素两两比较,看似是一个很小的步骤,但是却起到核心的作用。
需要注意的细节:
第一个循环, i是从=1开始的,从某种程度上,这是后面的j的行为决定的。(其实这里看的不是很明显,所以是从某种程度上。)不过要比较两个数,第二从0开始(j),那么第一个就要从1开始(i)
关键词/关键步骤:
对于for循环的问题,外层的行为某种程度上是由内层的行为决定的,虽然说知道了这一点对解决问题并没有什么用处。
————————————————————
选择排序:
我用的是swap。。。
不过我这个好像不是选择排序。。。无所谓了。。。
啊,这个叫做原地重排 ->
void rank_sort(int x[], int r[], const int size)
{
for(int i = 0; i < size; ++i)
{
r[i] = 0;
}
for(int i = 1; i < size; ++i)
{
for(int j = 0; j < i; ++j)
{
if(x[j] <= x[i])
++r[i];
else
++r[j];
}
}
for(int i = 0; i < size; ++i)
{
while(r[i] != i)
{
int t = r[i];
std::swap(x[i], x[t]);
std::swap(r[i], r[t]);
}
}
}
使用
int main(int argc, const char * argv[])
{
const int size = 5;
int array_toSort[size] = {5,4,3,2,2};
int array_rank[size] = {0};
rank_sort(array_toSort, array_rank, size);
for(int i = 0; i < size; ++i)
{
cout << array_toSort[i] << " ";
}
cout << endl;
return 0;
}
结果:
2 2 3 4 5
Program ended with exit code: 0
再试一次:
int main(int argc, const char * argv[])
{
const int size = 10;
int array_toSort[size] = {5,8,1,2,6,1,8,9,12,10};
int array_rank[size] = {0};
rank_sort(array_toSort, array_rank, size);
for(int i = 0; i < size; ++i)
{
cout << array_toSort[i] << " ";
}
cout << endl;
return 0;
}
1 1 2 5 6 8 8 9 10 12
Program ended with exit code: 0
————————————————————
————————————————————
好吧这才是正了八景的rank sort(需要一个附加数组的)
void rank_sort_2(int x[], int r[], const int size)
{
for(int i = 0; i < size; ++i)
{
for(int j = 0; j < i; ++j)
{
if(x[j] <= x[i])
++r[i];
else
++r[j];
}
}
int* array_temp = new int[size];
for(int i = 0; i < size; ++i)
{
array_temp[r[i]] = x[i];
}
for(int i = 0; i < size; ++i)
{
x[i] = array_temp[i];
}
delete[] array_temp;
}
使用:
int main(int argc, const char * argv[])
{
const int size = 10;
int array_toSort[size] = {5,8,1,2,6,1,8,9,12,10};
int array_rank[size] = {0};
rank_sort_2(array_toSort, array_rank, size);
for(int i = 0; i < size; ++i)
{
cout << array_rank[i] << " ";
}
cout << endl;
for(int i = 0; i < size; ++i)
{
cout << array_toSort[i] << " ";
}
cout << endl;
return 0;
}
结果:
3 5 0 2 4 1 6 7 9 8
1 1 2 5 6 8 8 9 10 12
再试一次:
int main(int argc, const char * argv[])
{
const int size = 12;
int array_toSort[size] = {4,8,1,77,6,1,8,14,67,32,5,16};
int array_rank[size] = {0};
//rank(array_toSort, array_rank, size);
//for(int i = 0; i < size; ++i)
//{
// cout << array_rank[i] << " ";
//}
//cout << endl;
rank_sort_2(array_toSort, array_rank, size);
for(int i = 0; i < size; ++i)
{
cout << array_rank[i] << " ";
}
cout << endl;
for(int i = 0; i < size; ++i)
{
cout << array_toSort[i] << " ";
}
cout << endl;
return 0;
}
结果:
2 5 0 11 4 1 6 7 10 9 3 8
1 1 4 5 6 8 8 14 16 32 67 77
Program ended with exit code: 0
总结起来一句话:
对于每一次for循环的i:((当然是从小到大)的顺序,或者是从大到小的顺序)
把i在数组x中对应位置的数字,放在i在数组r中对应位置的数字表示的新数组(temp)位置上。
关键词/关键动作
1 - 每一个循环的i对应两个数字,一是数据(x),二是位置(r)
2 - r中所存的数字的最大数字和size是对应的 = size - 1。这样才能正好在跑完r后将所有的temp数组的位置正好填满,没有重合,多余和遗漏。。。
————————————————————
选择排序 - selection sort
void selection_sort(int x[], const int size)
{
for(int i = size - 1; i > 0; --i)
{
int indexOfMax = 0;
for(int j = 0; j < i; ++j)
{
if(x[indexOfMax] < x[j+1])
indexOfMax = j+1;
}
std::swap(x[indexOfMax], x[i]);
}
}
使用
int main(int argc, const char * argv[])
{
const int size = 12;
int array_toSort[size] = {4,8,1,77,6,1,8,14,67,32,5,16};
int array_rank[size] = {0};
selection_sort(array_toSort, size);
for(int i = 0; i < size; ++i)
{
cout << array_toSort[i] << " ";
}
cout << endl;
return 0;
}
结果
1 1 4 5 6 8 8 14 16 32 67 77
Program ended with exit code: 0
没啥可说的
不过当时感觉选择最大值的那个方法还挺好玩的,给我一种一个球来回碰撞的感觉,谁大就传给谁,没有那个大就自己拿着
关键词:
返回最大值序号的函数;
int indexOfMax(int x[], const int size)
{
int iOfMax = 0;
for(int i = 1; i < size; ++i)
{
if(x[iOfMax] <= x[i])
iOfMax = i;
}
return iOfMax;
}
使用:
int main(int argc, const char * argv[])
{
const int size = 12;
int array_toSort[size] = {4,8,1,77,6,1,8,14,67,32,5,16};
int array_rank[size] = {0};
cout << indexOfMax(array_toSort, size) << endl;
return 0;
}
结果:
3
————————————————————
及时终止的选择排序:
void selection_sort_stop(int x[], const int size)
{
bool sorted = false;
for(int i = size - 1; !sorted && i > 0; --i)
{
sorted = true;
int indexOfMax = 0;
for(int j = 0; j < i; ++j)
{
if(x[indexOfMax] < x[j+1])
{
indexOfMax = j + 1;
sorted = false;
}
}
std::swap(x[i], x[indexOfMax]);
}
}
没啥可说的,就是,看来给bool变量赋很多次没什么用处的值,比判断多次要划得来一些。。。也许判断就是很费时间呢。。。
————————————————————
冒泡排序:
void bubble_sort(int x[], const int size)
{
for(int i = size - 1; i > 0; --i)
{
for(int j = 0; j < i; ++j)
{
if(x[j] > x[j+1])
std::swap(x[j], x[j+1]);
}
}
}
使用
int main(int argc, const char * argv[])
{
const int size = 12;
int array_toSort[size] = {4,8,1,77,6,1,8,14,67,32,5,16};
bubble_sort(array_toSort, size);
for(int i = 0; i < size; ++i)
{
cout << array_toSort[i] << " ";
}
cout << endl;
return 0;
}
结果
1 1 4 5 6 8 8 14 16 32 67 77
Program ended with exit code: 0
也没什么可说的。
1 - 至于外层的i在2(i > 1)还是停在1(i > 0),我想还是应该停在1,如果停在2,把0 - 2这几个数进行一次冒泡之后,仍有位置0大于位置1的可能。
书上的确有一个”i > 1“,但是要注意这个i,在内层变成“ n - 1",所以实质上应该还是停在1处,不过我也没有仔细推敲过,只是看起来像而已了
2 - 好玩的地方在于,对于这种算法,想是从左往右想的(的确有从右往左的成分),即第一次从左往右排一次,第二次从左往右排一次,第三次从左往右排一次。。。从右往左的成分就在于,每次排序的尽头在一个个向左移动,这体现在在外层的i,要从size - 1开始,然后一点点减小。
或者说,兴趣点在于,人类思考是先思考一次排序,然后考虑到尽头的问题,而写代码的时候,是现在外层套上一个个减小的尽头,然后再去写内层的一次次排序
————————————————————
及时终止的冒泡排序
void bubble_sort_stop(int x[], const int size)
{
bool sorted = false;
for(int i = size - 1; !sorted && i > 0; --i)
{
sorted = true;
for(int j = 0; j < i; ++j)
{
if(x[j] > x[j+1])
{
std::swap(x[j], x[j+1]);
sorted = false;
}
}
}
}
在“及时终止的选择排序”中忘了提的,同时也是“及时终止的冒泡排序”中出现的,在第一层for循环中出现的,一起判断sorted和i是否到达边界。
我相信这里有一点书中算法书中没有提,但是C++primer中应该有提到过,即&&如果左侧的条件没有达到,那么右边的条件就不会去判断了。
所以我坚持把判断sorted写在左边,因为!sorted是一个更容易不满足的条件。如果它达到了,就不用去判断右边了
————————————————————
在有序数组中插入一个数字
//在有序数组中插入一个数字
void insertInSorted(vector<int> &sorted, int n)
{
sorted.push_back(n);
for(int i = (int)sorted.size() - 2; sorted[i] > sorted[i + 1] && i >= 0; --i)
std::swap(sorted[i], sorted[i+1]);
}
在这里我用了vector,因为更方便与push_back
插入一个数字的时候一定要注意一定要判断这个数字一直到i = 0,因为很可能要和位于0位置上的数字也换一下。
另外,还是我自己的经验 - 一定要检验一下边界,比如插入一个最小的数字,插入一个最大的数字,等等
(for循环很大程度上考验的就是边界)
————————————————————
插入排序
void insertion_sort(int x[], const int size)
{
for(int i = 1; i < size; ++i)
{
for(int j = i - 1; x[j] > x[j+1] && j >= 0; --j)
std::swap(x[j], x[j+1]);
}
}
插入排序就是把“之前”的一部分都当成是一个排好顺序的数组,然后将这一部分的后一位看能插入到其中的什么位置。
仍然是在for循环中做判断,一开始我还不是很适应在for循环中做判断,后来写了几次,呃仍然不是很适应,有好多次我都把判断写在外面了。。。
需要注意的是,书上并没有用swap,算法书上的用到swap的次数要比我用的少的多。其实对于int来说用swap并没有直接赋值更经济,反而要浪费更多的时间。
————————————————————
二分查找
int binary_search(const vector<int> &sorted, int n)
{
int left = 0;
int right = sorted.size() - 1;
while(left <= right)
{
int middle = (left + right) / 2;
if(sorted[middle] == n)
return middle;
else if(n > sorted[middle])
left = middle + 1;
else
right = middle - 1;
}
return -1;
}
好吧我承认我写二分查找写的不够多,犯了好几个严重的错误(之前犯过的)而且最后还是照着书写出来的。写错的原因在于对于细节并不领悟(主旨早就知道了,但是并未完全理解细节)
首先left和right一定要等于middle 加1或减1,原因在于,如果left和right一直等于middle,这个程序永远都结束不了。
另外一个原因在于,middle既然小于或大于要查找的数,那么就和要查找的数就无关了,left和right应该跳过middle
边界条件仍然至关重要。二分法的边界条件在于可能有计数偶数两种情况。其实完全不用害怕,只不过两种情况而已
即使不是边界也是两种情况,如果是left加right是奇数,那么middle就是他们中间的那一个,如果是偶数,就是中间偏左的那个数。
到了边界的情况,如果剩下两个数,那么middle就是left,如果left/middle不是n,left的+1可以是判断挪到right,去判断是不是n
如果只剩下一个数,left和right是一样的,此时还要继续(满足while的条件,这也是为什么while的条件是left <= right),这时进行运算后middle也是这个数,left, 或right, 或middle, 判断这个middle是不是n。
另外,一定要注意不是middle是否等于n,二是数组的middle位是否等于n。
其实我也不是特别理解这么做的好与坏,我直接就读了算法的实现,并没有考虑过任何其它种情况。
————————————————————
参考 / 读书笔记读的书:
————————————————————
C++ Primer(第五版)ISBN 978-7-121-15535-2
数据结构,算法与应用:C++语言描述(第二版)ISBN 978-7-111-49600-7
更新于
2021.01.17