zl程序教程

您现在的位置是:首页 >  其他

当前栏目

重复数字全排列的全新去重思想(非循环)C语言

2023-03-07 09:01:58 时间

前言

本文介绍一种求有重复数字的全排列的去重思想,该算法思想在时间上优于目前使用最广泛的循环去重思想。


题目展示

参考题目:Acwing—递归实现排列型枚举II

参考题目:Leetcode—全排列II

因力扣的提交格式复杂,在这里用Acwing的题目做演示。

函数栈帧

若想了解该种思想,就要先知道什么是函数栈帧。

顾名思义,函数与栈和帧的结合,每调用一个函数就会在栈区上开辟一块空间,就像视频的每一帧一样,当函数调用结束后,这一帧也会随之销毁,在栈区开辟的空间也会还给操作系统。

如果我们在函数内部创建一个数组的话,每调用一次函数,就会创建一次这个数组,该数组只会保存在当前帧里,当函数调用结束,空间没了,这个数组自然也就没了。

f(int time)
{
	int st[10];
	f(time + 1);
}
int main()
{
	fun(1);
	return 0;
}

如果你运行上面的代码,就会出现下面这样的情况:

由于函数的每一帧都在栈区内保存,所以后面的帧销毁之前,前面的帧内创建的st数组会一直保存,本文要介绍的思想就利用了函数栈帧的该种性质。

去重思想

这里先展示代码,随后进行详细解读。

全局变量与主函数 (与正常全排列无区别)

int N = 0;
int data[10];
int arr[10];
int used[10];
//...
int main()
{
	scanf("%d", &N);
	int i = 0;
	for (i = 0; i < N; i++)
	{
		scanf("%d", &data[i]);
	}
	dfs(0);//递归用的函数
	return 0;
}

其中,N是数据的个数,data是保存数据的数组,arr是递归时保存每一帧状态的,used是记录某个数字是否被用过,这些变量与正常的全排列代码中的意义相同

函数

为了观看方便,将用图片展示,与正常全排列代码不同之处已用红色标出。

在本题中,step代表当前要排列的位置的下标,若想去重,就要保证每个数在每个位置上只出现一次,同时还不能影响其他分支

因此,我们在函数内部创建数组st,用来保存当前位置下有哪些数被用过了,被用过了就跳过这个数即可。

拿数据data10={1,1,3} 举例,第一个位置首先赋值为data0,同时让st[data0]+=1,表示在第一个位置data0 已经用过了,当第一个位置为data0 的分支全部结束后,就会把第一个位置赋为data1,但此时data0data1 相同,即st[data1]st[data0] 相同,都等于1,这样就把这第二个1给跳过了。

当我们进入到下一个位置时,又会创建一个新的st数组来保存当前位置的使用记录,这样前面的st数组既不会影响到后面的,也不会被后面的st数组覆盖,就能达到去重的效果。

错误示范

博主一开始并没有想到用函数栈帧的性质,而是创建一个10X10的全局数组,让每一行代表每个step(每个位置),每一列代表每个数,哪个位置的哪个数被用了,就让其自增1。

这样不就相当于有了十个一维数组,每个一位数组都代表每个位置的数,不也可以达成上文讲述的效果吗?

但是这样却忘记了一件事,数组的记录不能影响其他的分支,拿{1,1,3}举例。

下面将展示两张图,其中step代表位置,从上到下代表选择,每一个紫色框代表一帧


以上两张图便是一正确、一错误的两种方法最直观地解释了。

思想优劣

若是题目没有要求按照字典序输出,则这种思想与循环思想相比,最大的优势是不需要排序,节约了排序消耗的时间,降低了代码的复杂程度。

一般这种全排列题目,给定的N不会太大,所以排序的时间其实也很短,主要是降低了代码的复杂程度。

其次,即使题目要求按照字典序输出,该思想在时间上依然优于循环思想,Acwing上两种代码各提交十次去掉极值后取平均值,比循环思想快约20ms,几乎可以忽略不计。

由此得出:

  • 题目不要求字典序——优先选用本文介绍的思想
  • 题目要求字典序——两者皆可,差别不大

AC代码

下面展示Acwing的通过代码

#include<stdio.h>
#include<stdlib.h>
int n=0;
int arr[10];
int data[10];
int used[10];
f(int step)
{
    int st[10]={0};
    if(step==n)
    {
        int i=0;
        for(i=0;i<n;i++)
        {
            printf("%d ",arr[i]);
        }
        printf("\n");
        return;
    }
    int i=0;
    for(i=0;i<n;i++)
    {
        if(used[i]==0&&st[data[i]]==0)
        {
            used[i]=1;
            st[data[i]]=1;
            arr[step]=data[i];
            f(step+1);
            used[i]=0;
        }
    }
}
int cmp(const void* a,const void* b)
{
    int* _a=(int*)a;
    int* _b=(int*)b;
    return *(_a)-*(_b);
}
int main ()
{
    scanf("%d",&n);
    int i=0;
    for(i=0;i<n;i++)
    {
        scanf("%d",&data[i]);
    }
    qsort(data,n,4,cmp);
    f(0);
    return 0;
}

感谢您的阅读与耐心~ 如有错误烦请指出~ 谢谢~