前言
今天,我们要学一种不需要递归就能在一个数列里边生成全部唯一的序列的方法。这种方法呢,实现起来很简单,只要一个与平常进制不同的数,还有若干的简单交换操作,即可让某一个数列生成全部唯一的序列。
阶乘数
什么是阶乘数
在我教这个方法之前,先要给大家理解一下在生成全部唯一的序列的方法中所用到的一种特殊的数——阶乘数。那么,什么是阶乘数?阶乘数,就是每一位的进制数都比上一位的进制数多1,而且第1位的进制为2。
而阶乘数每一位的值,跟其他进制的数的值是一样原理的。比方说某一个二进制的数,就是10,它表示十进制的2,为什么呢?是因为二进制数的逢2进1的原理,当这个二进制数的第1位为2时,就会进1,然后第1位变为0,就变成了10。
再讲一个二进制数,是100,表示为十进制是4,原因呢,就是这个二进制数的第2的值位进行了一次逢2进1,通过刚才的例子,我们可以知道二进制数的第2位中的1所表示的就是十进制中的2,,在第2位逢2进1后,第3位就为1,第2位就为0,这个二进制数也就成100了。
以此类推,再举一个例子,就是二进制数1000,1000中的第4位需要第3位逢2进1,第3位的数字代表4,,逢2进1后,第3位成0,第4位成1,就成1000,因此这个二进制数为8。
通过刚才的这些案例中,我们可以知道一个规律——
因为二进制中的每一位都会逢2进1,当二进制数的某一位变成2时,他的前面一位就会加1,自身会变为0。
因此,现在转到阶乘数,根据刚才的规律,我们可以知道,阶乘数的第1位表示1,因为这一位后面没有其它位,第2位表示2,因为第1位是2进制,,第3位表示6,因为第2位是3进制,,第4位则表示24,因为第3位是4进制,。以此类推,就可以算出阶乘数的第几位是什么值了。
需要注意的是,阶乘数的每一位的值是有规律的,第1位是,第2位是,第3位是,第4位是,用阶乘表示是第1位是,第2位是,第3位是,第4位是,通过这个,我们可以用这个公式来求出阶乘数每一位的值:
知道了阶乘数求某一位的值的方法后,接下来就去讲阶乘数的转换了。
阶乘数的转换
阶乘数呢,跟其它数一样,也可以转换成各种各样的进制,其它数呢,也可以转换成阶进制。
N进制数转阶乘数
N进制数转阶乘数,要先把N进制数转为十进制数来计算,然后,就要用除运算和模运算来求出每一位的值,最后,在检测到的位数的值超过这个N进制数所代表的十进制数的时候,把每一位的值一一给原来为0的阶乘数加上去,就好了。下面就以八进制数144来演示N进制数转阶乘数的方法。
graph
a([开始])-->b[将八进制数144转为十进制数100]-->c[/"初始化一个记录阶乘数每一位的值的变量fact为1,一个表示进制数的变量radix为2和一个表示阶乘数的变量orderMultiplier为0"/]-->d{如果变量fact小于等于100这个十进制数}-->|那么进入循环|e[变量orderMultiplier的第radix - 1位自增100除以变量fact模上变量radix的结果]-->fact自乘变量radix-->radix自增1-->d----->|否则退出循环|z([结束])
阶乘数转N进制数
阶乘数转N进制数,只要先遍历阶乘数的每一位,然后再对某一个十进制数自增阶乘数每一位的数乘以每一位的值,最后将这个十进制数转换为N进制数,就行了。这里以阶乘数4020转换成八进制数的例子举例。
graph
a([开始])-->b[/"初始化一个记录阶乘数每一位的值的变量fact为1,一个表示进制数的变量radix为2,一个表示阶乘数某一位的位置的变量pos为0,和一个表示十进制数的变量decimalNum为0"/]-->c{"如果变量pos小于4020这个阶乘数的长度"}-->|那么进入循环|d[变量decimalNum自增4020这个阶乘数第pos + 1位的值乘以变量fact的结果]-->变量fact自乘变量radix-->变量radix自增1-->变量pos自增1-->c----->|否则退出循环|将十进制数变量decimalNum转换为八进制数-->z([结束])
知道了阶乘数与其它进制的数的相互转换后,阶乘数的运算你也该了如指掌了。
阶乘数的运算
阶乘数,跟其它的数一样,也能够加减乘除,但这里只讲它的加减,因为乘除运算在这篇博客里不重要。
阶乘数的加减运算,复杂的不管,简单的就跟十进制数的加减运算的原理几乎一样,因为阶乘数可以和N进制数相互转换,故此,可以先给某个N进制数加或减某一个数,之后再给这个数转为阶乘数,就行。方法跟阶乘数与N进制数的互相转换的方法差不多,这里不再演示。
操作方式
好了,在学关于阶乘数的一些主要知识后,就能开始生成全部唯一的序列的实现了。
首先,就需要我们刚才所提过的那个刚才与平常进制不同的数——阶乘数,这个数呢,就用一个普通的unsigned int无符号整型u_i来表示。
然后,为了让这个无符号整型变量会有像二进制一样的查找某一位中的数字的功能,我们就要新建一个名叫HierarchicalNum.h的头文件和一个名叫HierarchicalNum.cpp的源文件。
之后,就在头文件里声明下面的getPosNum方法,并在源文件里实现这个方法,具体的方法就跟N进制数转阶乘数的方法差不多,但比这个方法简单的是,只需要先求出index在阶乘数u_i中所代表的值digitNum,然后再求出当前位的进制数radix,最后返回u_i除以digitNum再模上radix的结果,就好了。
unsigned int getPosNum(const unsigned int u_i, const unsigned int index) {
unsigned int digitNum = 1;
unsigned int i = index + 1;
unsigned int radix = index + 2;
while (i > 1) {
digitNum *= i--;
}
return u_i / digitNum % radix;
}
接下来,就可以实现一个用于生成全部唯一的序列的类了。类名为Sequence,成员变量有用来当阶乘数的类型为无符号整数的swapNum,有限制swapNum的类型为无符号整数的maxNum,还有一个vector数组items和一个装vector数组的vector数组SequenceVector,为了使该类更通用,vector数组的类型就用泛型T表示。
template<class T>
class Sequence {
private:
vector<T>items;
vector<vector<T>>sequenceVector;
unsigned int maxNum;
unsigned int swapNum;
}
之后,就开始实现主要的方法了。
构造方法
首先,我们来实现构造方法。构造方法,要实现的有:无参构造方法,长度构造方法和vector数组构造方法。拷贝方法的话,由于没有用new开辟空间,所以这个方法可以忽略。
无参构造方法
无参构造方法,除了上面两个vector数组之外,其它的都要初始化。初始化的方式呢,就是将maxNum设为1,swapNum的值不用管,那为什么swapNum的值不用管呢,因为在执行生成序列的方法前,swapNum就会设为1。
Sequence() {
this->maxNum = 1;
this->swapNum = 0;
}
长度构造方法
长度构造方法,则是在无参构造方法的基础上再往items数组里添加数据,使items数组的长度达到该构造方法所需的长度,并根据数组的长度的阶乘值来设置maxNum的值,就行。
Sequence(unsigned int len) {
this->maxNum = 1;
this->swapNum = 0;
unsigned int factNum = len;
while (this->maxNum *= factNum, factNum-- > 1){}
while (len--) { this->items.push_back(0); }
}
vector数组构造方法
最后就是vector数组构造方法,则是在无参构造方法的基础上用构造方法所需的vector数组直接拷贝给items数组,并根据该数组的长度的阶乘值来设置maxNum的值,就行。
Sequence(const vector<T> f_items) {
this->items = f_items;
this->maxNum = 1;
this->swapNum = 0;
unsigned int factNum = this->items.size();
while (this->maxNum *= factNum, factNum-- > 1){}
}
简单方法
接下来,就要实现那些虽然简单,但也为下面的getSequence方法提供了便利的简单方法。
swapVectorItem和isUnique方法
先讲swapVectorItem方法,该方法顾名思义,就是交换items数组元素,要想指定这两个元素,就可以用简单的索引,因此,形参为两个无符号整型index和indexa,交换的方法,就是按平常的来。
void swapVectorItem(const unsigned int index, const unsigned int indexa) {
T tempItem = this->items[index];
this->items[index] = this->items[indexa];
this->items[indexa] = tempItem;
}
其次讲isUnique方法,isUnique方法,形参是vector数组,返回布尔值,用于在使用getSequence方法时检测某个序列是否不在sequenceVector数组中。
举个例子,假设这个要检测的序列叫items序列,那么,检测v序列是否不在sequenceVector数组中,只需要遍历一下sequenceVector数组,对该数组中的每一个元素进行判断,如果该数组中的某一个序列跟v序列一样,那么,返回false,否则就返回true。
bool isUnique(const vector<T> checkVector) {
for (vector<T> v : this->sequenceVector) {
if (checkVector == v) {
return false;
}
}
return true;
}
pushBackItem和popBackItem方法
接着讲pushBackItem方法,pushBackItem方法,形参和返回值都是T泛型,但是,你别单单想只要往items数组里面添加数组就行了,其实,还有maxNum这个成员变量要变,因为如果maxNum不更新,那么就会导致在使用getSequence方法时不能获取items数组的全部的序列。
因此,真正要想的,还是maxNum如何变的问题,那么,maxNum的值该怎样定呢?maxNum这个成员变量,它的值就是items数组的长度的阶乘值,因此,我们就只需要在往items数组添加数据的时候乘上添加完数据后items数组的长度,就行。之后就可以返回添加的数据了。
T pushBackItem(const T item) {
this->items.push_back(item);
this->maxNum *= this->items.size();
return item;
}
再讲popBackItem方法,popBackItem方法,返回值也是T泛型,只不过无参,用于删除items数组最后面的元素,同时,删除元素的时候也要注意这两点:
-
当
items数组大小为0时则不能删除,因为在items数组大小为0的时候,由于没有元素,就不能删了。并且之后直接返回NULL。 -
删好元素之前,
maxNum的值要先变,至于怎么个变法,就只要让maxNum除以删元素之前的items数组长度的值,就行。 -
如果
items数组可以删元素,那么就先用一个类型为泛型T的临时变量来存储items数组的最后面的元素,之后删除后就返回这个临时变量。
这三步做完之后,popBackItem方法也就实现好了。
T popBackItem() {
if (this->items.size()) {
T item = this->items.back();
this->maxNum /= this->items.size();
this->items.pop_back();
return item;
}
else {
return NULL;
}
}
sequenceCount方法
sequenceCount方法,返回值的类型为无符号整型,无参,用来获取items数组的全部唯一的序列有多少条。相当于获取sequenceVector数组的大小,因此,在这个方法中可以直接返回sequenceVector数组的大小,并且,由于该方法没有赋值操作,所以该方法也可以声明为常方法。
unsigned int sequenceCount() const {
return this->sequenceVector.size();
}
getSequence方法
最后一个要实现的方法,getSequence方法来了。getSequence方法实现的第一步,就是清空sequenceVector的元素,可以防止之前的序列和现在用getSequence获得的序列被混在一个sequenceVector数组中。
然后第二步,就是检测items数组是否大小为0,如果items数组大小为0,那么就直接往sequenceVector里面添加空数组items,之后就直接返回sequenceVector数组;而如果items数组大小不为0,那么items数组接下来就可以正常获取items数组的序列。
接下来就是第三步,获取items数组的序列,那么,items数组的序列该怎样通过刚才提到的阶乘数来获取呢?这里且听我娓娓道来。
-
创建一个临时的
vector数组tempItems,并拷贝数组items的数据,以在获取items数组的其中一条序列后进行还原操作。 -
设置
swapNum阶乘数的值为1,然后初始化index的值为0,而因为swapNum阶乘数的有用的位的数量最多只能是maxNum减1的值,并且maxNum又等于items数组大小的阶乘值,所以还要再初始化swapIndex的值为items数组的大小减2的结果。 -
这些操作做好之后,就先往
sequenceVector数组里面添加尚未改动的items数组,因为尚未改动的items数组也是items数组的其中一条序列。之后一切准备完成,获取序列的操作就开始了。 -
在获取序列的操作执行之前,创建一个
while循环,这个循环的条件是“如果swapNum小于maxNum,那么就继续执行”,这样就可以根据取值范围为1到maxNum的阶乘数swapNum与若干交换操作来获取items数组的全部唯一的序列。 -
之后,设
swapIndex为items数组的大小减2的结果,设index为0,并新建一个for循环,循环条件为“如果index小于items数组的大小减1的结果就执行循环体内语句”。 -
在
for循环体内,用swapVectorItem方法来交换items数组的元素,要交换的元素索引的值在index和index加swapNum阶乘数的第swapIndex位的值的结果中,如果想求出swapNum阶乘数某一位的值,就用刚才实现的getPosNum方法来获取。 -
一次交换之后,
swapIndex自减1,index自加1,如果for循环条件成立,则继续循环。否则,就用isUnique方法检测现在交换后的items数组是否不在sequenceVector里面,如果在,就添加交换之后的items数组,否则就不执行,从而获取items数组全部唯一的某一条序列。 -
获取好
items数组的某一条序列后,maxNum自增1,并把tempItems的数据拷贝到items数组中,然后进入下一次while循环,以此来获取获取items数组的另一条序列。 -
while循环结束之后,最后返回sequenceVector数组,实现也就结束了。
vector<vector<T>> getSequence() {
this->sequenceVector.clear();
if (this->items.size()) {
this->sequenceVector.push_back(this->items);
this->swapNum = 1;
int digitNum = this->items.size() - 1;
int swapIndex = this->items.size() - 2;
int index = 0;
vector<T>tempItems = this->items;
while (swapNum < this->maxNum) {
for (swapIndex = this->items.size() - 2, index = 0; index < this->items.size() - 1; index++, swapIndex--) {
swapVectorItem(index, index + getPosNum(swapNum, swapIndex));
}
if (isUnique(this->items)) {
this->sequenceVector.push_back(this->items);
}
this->items = tempItems;
swapNum++;
}
}
else {
this->sequenceVector.push_back(this->items);
}
return this->sequenceVector;
}
getSequence方法的原理
那么,为什么items数组通过这样的交换操作就能获取到全部唯一的序列呢?就以只有两个元素1和2的items数组为例,如果想要求出它的全部唯一序列,就只需要先把原先的items数组添加进sequenceVector数组里,再交换一下items数组里面索引为0和1的元素,最后把交换后的items添加进sequenceVector数组里,items数组的其中一条序列就获取到了。
在一次获取序列的操作过后,就检测items数组在交换之后是否仍然是恢复原状之后的样子,是就意味着items数组的全部唯一序列已全部获取到,之后就可以不再执行获取序列的操作了,不是就说明items数组还有序列没有获取到,因此,之后就把items数组恢复原状,以此获得items数组的另一条序列。
然后再以只有三个元素1,2和3的items数组举例,想要求出他的全部唯一序列,则变得复杂了。以这张items数组的序列图进行理解。
首先需要把还没交换过的items数组添加到sequenceVector数组里,根据之前获取只有两个元素的items数组的全部唯一序列的经验,我们先直接对索引为1和2的元素进行交换,再把交换后的items添加进sequenceVector数组里,并检测items数组在交换之后是否仍然是恢复原状之后的样子。
之后,由于还有索引为1和2的元素没和索引为0的元素进行交换,并且在与索引为1或2的元素交换过后,索引为1和2的元素也没有进行交换,所以在这一次检测之后,将items数组恢复原状,并先把索引为0和1的元素进行交换。然后将交换之后的items数组添加进sequenceVector数组里,并检测交换之后的items数组是否与原来的items数组保持不变,之后就将items数组恢复原状。
接着跟刚才的获取items序列的操作差不多,只不过这次先交换索引为0和1的元素,再交换索引为1和2的元素,交换好后将现在的items数组添加到sequenceVector数组里,之后检测交换之后的items数组是否与原来的items数组保持不变,并将items数组恢复原状,以此类推,直到items数组在交换之后仍然是恢复原状之后的样子,获取items数组全部唯一序列的操作也就结束了。
通过上面的例子,我们可以将每次获取只有2个元素items序列中的所有交换操作抽象成一个特殊的数1。这个1呢,代表了刚才items数组索引为0的元素与索引为0加上这个特殊的数1的第1位的结果的元素进行了交换。
以此类推,我们可以将每次获取只有3个元素items序列中的所有交换操作抽象成一串特殊的数:
首先讲这串数的第一个数01,01表示第一次items数组索引为0的数据跟索引为0加上01的第2位的数的结果的元素进行了交换,第二次items数组索引为1的数据跟索引为1加上01的第1位的数的结果的元素进行了交换,根据这个数,sequenceVector数组获取了items数组的一条序列。
然后讲这串数的第二个数10,10表示第一次items数组索引为0的数据跟索引为0加上10的第2位的数的结果的元素进行了交换,第二次items数组索引为1的数据跟索引为1加上10的第1位的数的结果的元素进行了交换,根据这个数,sequenceVector数组又获取了items数组的一条序列。
最后,你们有没有发现这一串的抽象过的数很像刚才我们提过的阶乘数,阶乘数从1到5是这样表示的——1,10,11,20,21,如果1保留两位的话就是01,那么我们可以把刚才的items数组的序列图抽象成这样。
通过这些阶乘数,我们可以得出一个items数组的所有序列。那么,交换操作是怎样跟阶乘数产生联系的呢?这个问题的答案很简单,假设阶乘数的第位的数为,阶乘数的位数为,交换的items数组的第一个元素索引为,交换的items数组的第二个元素索引为,那么可以得出两条公式:和,同时,这条公式里面的和也分别与上面getSequence方法中的index和getPosNum方法的返回值。
同时,我们可以知道,如果items数组交换后依然是原样,那就说明交换操作所对应的阶乘数为0,即图中的00。假如这个阶乘数只用了两位,即第1位与第2位,且阶乘数的位数依然为,那么根据刚才的这条公式,判断items数组交换后依然是原样还有一种——,而公式中的,就相当于getSequence方法中的maxNum。
而items数组的所有序列如何变得唯一的问题,只要一个isUnique方法检测items数组的某一条序列是否不在sequenceVector数组中就可以搞定,不必多说。至此,getSequence方法的原理也就讲完了。
实际测试
Sequence类实现好后,就用这个代码进行测试。
#include <iostream>
#include "Sequence.hpp"
using namespace std;
int main() {
vector<int>v = { 1, 2, 3, 4 };
Sequence<int>s;
cout << "无参构造方法已执行!" << endl;
Sequence<int>sa(4);
cout << "长度构造方法已执行!" << endl;
Sequence<int>sc = v;
cout << "数组构造方法已执行!" << endl;
s.popBackItem();
cout << "在items数组为空的情况下,popBackItem方法已执行!" << endl;
sa.pushBackItem(0);
cout << "pushBackItem方法已执行!" << endl;
sa.popBackItem();
cout << "正常情况下,popBackItem方法已执行!" << endl;
vector<vector<int>>vv = s.getSequence();
cout << "空items数组已正常获取全部唯一序列!" << endl;
vector<vector<int>>vva = sa.getSequence();
cout << "元素不唯一的items数组已正常获取全部唯一序列!" << endl;
vector<vector<int>>vvb = sc.getSequence();
cout << "元素唯一的items数组已正常获取全部唯一序列!" << endl;
cout << "空items数组全部唯一序列:" << endl;
for (vector<int> v : vv) {
for (int i : v) {
cout << i << " ";
}
cout << endl;
}
cout << endl << "元素不唯一的items数组全部唯一序列:" << endl;
for (vector<int> v : vva) {
for (int i : v) {
cout << i << " ";
}
cout << endl;
}
cout << endl << "元素唯一的items数组全部唯一序列:" << endl;;
for (vector<int> v : vvb) {
for (int i : v) {
cout << i << " ";
}
cout << endl;
}
cout << endl << "空items数组全部唯一序列个数:" << s.sequenceCount() << endl;
cout << "元素不唯一的items数组全部唯一序列个数:" << sa.sequenceCount() << endl;
cout << "元素唯一的items数组全部唯一序列个数:" << sc.sequenceCount() << endl;
return 0;
}
如果打印出来是这样的,那么就说明你的Sequence类就基本完成好了。
至此,你已经完成了在items数组里面生成全部唯一的序列的操作了。
下篇预告
盘点shell中对数以万计的IT人来说非常重要的特殊变量!