前言
今天,我们要学一种不需要递归就能在一个数列里边生成全部唯一的序列的方法。这种方法呢,实现起来很简单,只要一个与平常进制不同的数,还有若干的简单交换操作,即可让某一个数列生成全部唯一的序列。
阶乘数
什么是阶乘数
在我教这个方法之前,先要给大家理解一下在生成全部唯一的序列的方法中所用到的一种特殊的数------阶乘数。那么,什么是阶乘数?阶乘数,就是每一位的进制数都比上一位的进制数多1
,而且第1
位的进制为2
。
而阶乘数每一位的值,跟其他进制的数的值是一样原理的。比方说某一个二进制的数,就是10
,它表示十进制的2
,为什么呢?是因为二进制数的逢2
进1
的原理,当这个二进制数的第1
位为2
时,就会进1
,然后第1
位变为0
,就变成了10
。
再讲一个二进制数,是100
,表示为十进制是4
,原因呢,就是这个二进制数的第2
的值位进行了一次逢2
进1
,通过刚才的例子,我们可以知道二进制数的第2
位中的1
所表示的就是十进制中的2
, <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 × 2 = 4 \mathbf{2\times2=4} </math>2×2=4,在第2
位逢2
进1
后,第3
位就为1
,第2
位就为0
,这个二进制数也就成100
了。
以此类推,再举一个例子,就是二进制数1000
,1000
中的第4
位需要第3
位逢2
进1
,第3
位的数字代表4
, <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 × 2 = 8 4\times2=8 </math>4×2=8,逢2
进1
后,第3
位成0
,第4
位成1
,就成1000
,因此这个二进制数为8
。
通过刚才的这些案例中,我们可以知道一个规律------
<math xmlns="http://www.w3.org/1998/Math/MathML"> 二进制数的第 n − 1 位所表示的数 = 二进制数的第 n 位所表示的数 ÷ 进制数 \mathbf{二进制数的第n-1位所表示的数=二进制数的第n位所表示的数\div进制数} </math>二进制数的第n−1位所表示的数=二进制数的第n位所表示的数÷进制数
因为二进制中的每一位都会逢2
进1
,当二进制数的某一位变成2
时,他的前面一位就会加1
,自身会变为0
。
因此,现在转到阶乘数,根据刚才的规律,我们可以知道,阶乘数的第1
位表示1
,因为这一位后面没有其它位,第2
位表示2
,因为第1
位是2
进制, <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 × 1 = 2 2\times1=2 </math>2×1=2,第3
位表示6
,因为第2
位是3
进制, <math xmlns="http://www.w3.org/1998/Math/MathML"> 3 × 2 × 1 = 6 3\times2\times1=6 </math>3×2×1=6,第4
位则表示24
,因为第3
位是4
进制, <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 × 3 × 2 × 1 = 24 4\times3\times2\times1=24 </math>4×3×2×1=24。以此类推,就可以算出阶乘数的第几位是什么值了。
需要注意的是,阶乘数的每一位的值是有规律的 ,第1
位是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1,第2
位是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 2 </math>2,第3
位是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 6 6 </math>6,第4
位是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 24 24 </math>24,用阶乘表示是第1
位是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 ! \mathbf{1!} </math>1!,第2
位是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 ! \mathbf{2!} </math>2!,第3
位是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 3 ! \mathbf{3!} </math>3!,第4
位是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 ! \mathbf{4!} </math>4!,通过这个,我们可以用这个公式来求出阶乘数每一位的值:
<math xmlns="http://www.w3.org/1998/Math/MathML"> 阶乘数第 n 位 = n ! \mathbf{阶乘数第n位=n!} </math>阶乘数第n位=n!
知道了阶乘数求某一位的值的方法后,接下来就去讲阶乘数的转换了。
阶乘数的转换
阶乘数呢,跟其它数一样,也可以转换成各种各样的进制,其它数呢,也可以转换成阶进制。
N进制数转阶乘数
N进制数转阶乘数,要先把N进制数转为十进制数来计算 ,然后,就要用除运算和模运算来求出每一位的值 ,最后,在检测到的位数的值超过这个N进制数所代表的十进制数的时候,把每一位的值一一给原来为0的阶乘数加上去 ,就好了。下面就以八进制数144
来演示N进制数转阶乘数的方法。
阶乘数转N进制数
阶乘数转N进制数,只要先遍历阶乘数的每一位 ,然后再对某一个十进制数自增阶乘数每一位的数乘以每一位的值 ,最后将这个十进制数转换为N进制数,就行了。这里以阶乘数4020
转换成八进制数的例子举例。
知道了阶乘数与其它进制的数的相互转换后,阶乘数的运算你也该了如指掌了。
阶乘数的运算
阶乘数,跟其它的数一样,也能够加减乘除,但这里只讲它的加减,因为乘除运算在这篇博客里不重要。
阶乘数的加减运算,复杂的不管,简单的就跟十进制数的加减运算的原理几乎一样,因为阶乘数可以和N进制数相互转换,故此,可以先给某个N进制数加或减某一个数,之后再给这个数转为阶乘数,就行。方法跟阶乘数与N进制数的互相转换的方法差不多,这里不再演示。
操作方式
好了,在学关于阶乘数的一些主要知识后,就能开始生成全部唯一的序列的实现了。
首先,就需要我们刚才所提过的那个刚才与平常进制不同的数------阶乘数,这个数呢,就用一个普通的unsigned int
无符号整型u_i
来表示。
然后,为了让这个无符号整型变量会有像二进制一样的查找某一位中的数字的功能,我们就要新建一个名叫HierarchicalNum.h
的头文件和一个名叫HierarchicalNum.cpp
的源文件。
之后,就在头文件里声明下面的getPosNum
方法,并在源文件里实现这个方法,具体的方法就跟N进制数转阶乘数的方法差不多,但比这个方法简单的是,只需要先求出index
在阶乘数u_i
中所代表的值digitNum
,然后再求出当前位的进制数radix
,最后返回u_i
除以digitNum
再模上radix
的结果,就好了。
cpp
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
表示。
cpp
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
。
cpp
Sequence() {
this->maxNum = 1;
this->swapNum = 0;
}
长度构造方法
长度构造方法,则是在无参构造方法的基础上再往items
数组里添加数据,使items
数组的长度达到该构造方法所需的长度,并根据数组的长度的阶乘值来设置maxNum
的值,就行。
cpp
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
的值,就行。
cpp
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
,交换的方法,就是按平常的来。
cpp
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
。
cpp
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
数组的长度,就行。之后就可以返回添加的数据了。
cpp
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
方法也就实现好了。
cpp
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
数组的大小,并且,由于该方法没有赋值操作,所以该方法也可以声明为常方法。
cpp
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
数组,实现也就结束了。
cpp
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
数组的所有序列 。那么,交换操作是怎样跟阶乘数产生联系的呢?这个问题的答案很简单,假设阶乘数的第 <math xmlns="http://www.w3.org/1998/Math/MathML"> n \mathbf{n} </math>n位的数为 <math xmlns="http://www.w3.org/1998/Math/MathML"> x \mathbf{x} </math>x,阶乘数的位数为 <math xmlns="http://www.w3.org/1998/Math/MathML"> b \mathbf{b} </math>b,交换的items
数组的第一个元素索引为 <math xmlns="http://www.w3.org/1998/Math/MathML"> i \mathbf{i} </math>i,交换的items
数组的第二个元素索引为 <math xmlns="http://www.w3.org/1998/Math/MathML"> j \mathbf{j} </math>j,那么可以得出两条公式: <math xmlns="http://www.w3.org/1998/Math/MathML"> i = b − n \mathbf{i=b-n} </math>i=b−n和 <math xmlns="http://www.w3.org/1998/Math/MathML"> j = i + x \mathbf{j=i+x} </math>j=i+x,同时,这条公式里面的 <math xmlns="http://www.w3.org/1998/Math/MathML"> x \mathbf{x} </math>x和 <math xmlns="http://www.w3.org/1998/Math/MathML"> i \mathbf{i} </math>i也分别与上面getSequence
方法中的index
和getPosNum
方法的返回值。
同时,我们可以知道,如果items
数组交换后依然是原样,那就说明交换操作所对应的阶乘数为0
,即图中的00
。假如这个阶乘数只用了两位,即第1
位与第2
位,且阶乘数的位数依然为 <math xmlns="http://www.w3.org/1998/Math/MathML"> b b </math>b,那么根据刚才的 <math xmlns="http://www.w3.org/1998/Math/MathML"> 阶乘数第 n 位 = n ! 阶乘数第n位=n! </math>阶乘数第n位=n!这条公式,判断items
数组交换后依然是原样还有一种------ <math xmlns="http://www.w3.org/1998/Math/MathML"> 阶乘数 ⩾ b ! \mathbf{阶乘数\geqslant b!} </math>阶乘数⩾b!,而公式中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> b ! \mathbf{b!} </math>b!,就相当于getSequence
方法中的maxNum
。
而items
数组的所有序列如何变得唯一的问题,只要一个isUnique
方法检测items
数组的某一条序列是否不在sequenceVector
数组中就可以搞定,不必多说。至此,getSequence
方法的原理也就讲完了。
实际测试
Sequence
类实现好后,就用这个代码进行测试。
cpp
#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人来说非常重要的特殊变量!