排列树和子集树

版权声明:私藏源代码是违反人性的罪恶行为!博客转载无需告知,学无止境。 https://blog.csdn.net/qq_41822235/article/details/85992413

一、排列树

1.1 背景

系统排列最早由德国的治疗师伯特海灵格创立而来。这种治疗方法里,会把个人放在一个更大的整体里面来观察,如家庭系统或一个组织,而不是把个人看做一个独立的实体。只要把爱的序列搞好了,把家庭关系处理好了,其他的关系才能跟着和谐发展。

1.2 问题描述

当所给问题的确定n个元素满足某种性质的排列时,相应的解空间树称为排列树。排列树有n!个叶子结点。因此时间复杂度为O(n!)。

图1 ABC的全排列问题

如图1 所示,三个元素待排列,叶子结点数目为6片=3*2*1。 

1.3 数学归纳 

本质是n个元素的全排列问题。抓住本质以后,如何用语言来描述呢?如图1 所示,红色表示该位置已经固定。引入概念:固定的部分称为前缀,其余部分称为后缀。递归算法perm

设R={r1,r2,…,rn}是要进行排列的n个元素,Ri=R-{ri}(取补集):

  • 当n=1时,perm(R)=(r),其中r是集合R中唯一的元素;
  • 当n>1时,perm(R)由(r1)perm(R1),(r2)perm(R2),…,(rn)perm(Rn)构成。由n项构成,每项结构相同,会引导我们考虑循环

从定义中,我们意识到:我们要学会缩减问题规模。 我们只关心本层和下一层之间的关系,下下一层不去考虑。递归的思想大致就是如此了。

1.4 实现

函数声明 

#include<iostream>
#include<assert.h>
using namespace std;

template<typename T>    
void printArry(T *arr, int n);     //打印输出数组元素

template<typename T>
void myswap(T &a, T &b);    //交换数组元素

template<typename T>
static void perm_1(T *arr, int left, int right);    //全排列经典算法

template<typename T>
void perm(T *arr, int n);     //对外接口API

函数实现 

template<typename T>
void printArry(T *arr, int n)
{
    for(int i = 0; i < n; ++i)
        cout<<arr[i];
    cout<<endl;
}

template<typename T>
void myswap(T &a, T &b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

template<typename T>
static void perm_1(T *arr, int left, int right)
{
    assert(arr != NULL && right >= 0);
    if(left <= right)    //还有多个元素等待排列,递归产生排列
    {
        for(int i = left; i <= right; ++i)
        {
            myswap(arr[left], arr[i]);
            perm_1(arr, left+1, right);
            myswap(arr[left], arr[i]);
        }
    }
    else    //只剩下1个元素,输出路径经过的结点
    {
        printArry(arr, right+1);
    }
}

template<typename T>
void perm(T *arr, int n)
{
    perm_1(arr, 0, n-1);
}

测试用例 观察数字123的全排列结果

int main()
{
    int arr[] = {1,2,3};
    int n = sizeof(arr)/sizeof(arr[0]);
    perm(arr, n);
    return 0;
}

 运行结果

1.5 学以致用

旅行员售货和圆排列(回溯法),更高层次的旅行员售货问题(分支限界法)

猜你喜欢

转载自blog.csdn.net/qq_41822235/article/details/85992413