输出有序序列中元素的排名
给定一个有序序列,假设为:
1, 2, 2, 8, 9, 9, 10
我们想得到各个元素在序列中的排名,比如1的排名为1,第一个2和第二个2的排名都为2,即我们想得到这样一个序列:
1, 2, 2, 4, 5, 5, 7
我们首先讨论根据一个有序序列,如何得到其各个元素的排名;然后,讨论针对某个元素,如何快速得得到其排名。
一、得到整个序列各个元素的排名
程序如下:
#include <iostream> #include <vector> using namespace std; // 检测是否有序 bool is_ordered(const vector<int>& arr) { for (vector<int>::size_type i = 0; i != arr.size()-1; ++i) { if (arr[i] > arr[i+1]) { return false; } } return true; } bool ele_rank(const vector<int>& arr, vector<int>& arr_rank) { if (!is_ordered(arr) || arr.empty()) { return false; } arr_rank.clear(); arr_rank.resize(arr.size()); int rnk = 1; arr_rank[0] = rnk; for (vector<int>::size_type i = 1; i != arr.size(); ++i) { if (arr[i] == arr[i-1]) { arr_rank[i] = rnk; } else { rnk = i+1; arr_rank[i] = rnk; } } return true; } int main() { int a[] = {1, 2, 2, 8, 9, 9, 10}; vector<int> arr(a, a+sizeof (a)/sizeof (*a)); /* // vector<int> 中int元素是否被初始化了? vector<int> test(5); cout << test.size() << endl; for (vector<int>::size_type i = 0; i != test.size(); ++i) { cout << test[i] << ' '; } cout << endl; // 经检测:vector<int> 中的int元素是被初始化的,初始化为0。 */ vector<int> arr_rank; ele_rank(arr, arr_rank); for (vector<int>::size_type i = 0; i != arr.size(); ++i) { cout << arr[i] << ": " << arr_rank[i] << endl; } cout << endl; system("PAUSE"); return 0; }
程序逻辑就是顺序扫描,判断相邻两个元素是否相等,有个变量rnk记录当前排名,如果相等,则直接赋值为rnk,如果不相等,则修改rnk为i+1,然后赋值为rnk。
由于顺序扫描,所以我们的时间复杂度为O(N)。
二、快速查找某个元素的排名
以上得到所有元素的排名,其时间复杂度为O(N),我们可以对有序序列进行预处理,然后建立一个元素到坐标的映射,针对元素查找到坐标,然后根据坐标查找到其排名。
其中,预处理的时间复杂度为O(N);
建立元素到坐标的映射,其时间复杂度也为O(N);
针对元素查找到坐标位置,可以直接利用二分算法进行查找,时间复杂度为
O(NlogN);可以不用建立元素到坐标的映射,如果是二分法查找坐标位置,
可以不用建立映射,直接在arr上进行二分查找即可。如果想得到O(1)的
查找效率,那么有必要建立一个元素-坐标哈希映射。
得到坐标后,直接查找arr_rank即可。
我们在《计算序列中元素的位置》中讨论了如何通过二分法查找有重复元素的非降序序列中元素的位置和逆位置,我们可以借助于方法快速查找某个元素的排名。
具体做法为:针对待查找元素,通过二分算法查找到其位置pos,那么该元素在序列中的排名为pos+1。
具体的程序如下:
#include <iostream> #include <vector> using namespace std; // 检测是否有序 bool is_ordered(const vector<int>& arr) { for (vector<int>::size_type i = 0; i != arr.size()-1; ++i) { if (arr[i] > arr[i+1]) { return false; } } return true; } // 请参考《计算序列中元素的位置》:完全二分repeat_binary_2 void find_rnk(const vector<int>& seq, int n/*, int& pos, int& rpos*/, int& rnk) { // pos = rpos = -1; rnk = -1; int pos = -1; if (n < seq[0] || n > seq[seq.size()-1]) { return; } int left = 0, right = seq.size() - 1; int middle = 0; // find pos while (left <= right) { middle = (left + right) / 2; if (n == seq[middle]) { if (middle > left && n == seq[middle-1]) { right = middle - 1; continue; } else { pos = middle; rnk = pos + 1; // 根据pos得到rnk。 break; } } else if (n < seq[middle]) { right = middle - 1; } else { left = middle + 1; } } /* // find rpos left = 0; right = seq.size() - 1; middle = 0; while (left <= right) { middle = (left + right) / 2; if (n == seq[middle]) { if (middle < right && n == seq[middle+1]) { left = middle + 1; continue; } else { rpos = seq.size() - middle - 1; break; } } else if (n < seq[middle]) { right = middle - 1; } else { left = middle + 1; } } */ return; } int main() { int a[] = {1, 2, 2, 8, 9, 9, 10}; vector<int> arr(a, a+sizeof (a)/sizeof (*a)); int rnk = 0; for (vector<int>::size_type i = 0; i != arr.size(); ++i) { find_rnk(arr, arr[i], rnk); cout << arr[i] << ": " << rnk << endl; } find_rnk(arr, 33, rnk); cout << 33 << ": " << rnk << endl; cout << endl; system("PAUSE"); return 0; }
本算法针对单个元素的查找时间复杂度为O(logN)。
三、讨论
我们主要讨论了两个方面:
1.对整个序列计算每个元素的排名,这种时间复杂度为O(N);
2.对序列中某个元素查找器排名,这种时间复杂度为O(logN)
一般情况下对整体的操作,可以看做是预处理操作,经过预处理操作后,后续的查询等操作效率将有数量级的提高。
对于经常需要大批量查找的情况,尽量先做一个预处理过程,然后再后续大量查找中可以节省很多时间。
如果只是少量查找,那么就没必要进行预处理了,而是尽量针对单个元素进行优化。