三种全排序算法详解

1、全排列的非去重递归算法

算法思路:全排列可以看做固定前i位,对第i+1位之后的再进行全排列,比如固定第一位,后面跟着n-1位的全排列。那么解决n-1位元素的全排列就能解决n位元素的全排列了,这样的设计很容易就能用递归实现。

附代码段:

void permutation1(char* str,int sbegin,int send)    //全排列的非去重递归算法
{
    if( sbegin == send) //当 sbegin = send时输出
    {
        for(int i = 0;i <= send; i++)   //输出一个排列
            cout << str[i];
        cout << endl;
    }
    else
    {
        for(int i = sbegin; i <= send; i++) //循环实现交换和sbegin + 1之后的全排列
        {
            swap(str[i],str[sbegin]);   //把第i个和第sbegin进行交换
            permutation1(str,sbegin + 1,send);
            swap(str[i],str[sbegin]);   //交换回来
        }
    }
}

2、全排列的去重递归算法

序列有时候可能有重复的字符,当需要输出去重全排列时,就可以采取以下方法

当第i位与i+n位交换时,i到i+n位中不能有与i+n位相同的字符

比如说23560678,当第三位5与第六位6交换时,中间不能有6,这里有6所以不进行交换

附代码段:

bool comparesub(char* str,int sbegin,int send)  //比较函数,[sbegin,send)中是否有与send的值相等得数
{
    for(int i = sbegin; i < send; i++)
        if(str[i] == str[send])
            return false;
    return true;
}

void permutation2(char* str,int sbegin,int send)    //全排列的去重递归算法
{
    if(sbegin == send)  //当 sbegin = send时输出
    {
        for(int i = 0; i <= send; i++)  //输出一个排列
            cout << str[i];
        cout << endl;
    }
    else
    {
        for(int i = sbegin; i <= send; i++)
        {
            if(comparesub(str,sbegin,i))    //如果sbeing到i没有重复数字
            {
                swap(str[i],str[sbegin]);   //把第i个和第sbegin进行交换
                permutation2(str,sbegin + 1,send);
                swap(str[i],str[sbegin]);   //交换回来
            }
        }
    }
}

3、全排列的去重非递归算法

算法思路:先排序得到递增序列,从字符串尾部向前找第一双相邻的递增字符,称前一个数为替换字符,替换字符的下标称为替换点,再从该字符后面找到一个比替换字符大的最小的字符(因为有递增关系,所以这个数必然存在的),然后再将替换点后的字符串逆置。循环到最大排序后,逆置并结束算法

例:字符串2568710 先排序得到0125678找到一双相邻递增字符,78,再在7后面的字符里找到符合算法的字符8,替换得到字符串0125687,再将7后面的字符串逆置,得到0125687

附代码段:

void Reverse(char* a,char* b)   //逆置函数
{
    while(a < b)
    {
        char tmp = *a;
        *a = *b;
        *b = tmp;
        a++;
        b--;
    }
}

bool next_permutation3(char* str)   //找到一个满足算法的序列
{
    int i;
    int slen = strlen(str); //str长度
    for(i = slen - 1; i >= 1; i--)  //循环找出一双相邻递增字符
    {
        if(str[i - 1] < str[i])
            break;
    }
    if(!i)  //如果i=0,证明没有一双相邻递增字符,那么也就说明整个字符串是最大排列
    {
        return false;   //结束算法
    }
    else
    {
        char tmp = str[i - 1];  //替换字符
        int pos = i;            //比替换字符大的最小的字符位置
        for(int j = i; j < slen; j++)
        {
            if(str[j] > tmp && str[j] <= str[pos])  //从替换字符后面找到一个比替换字符大的最小的字符
                pos = j;
        }
        str[i - 1] = str[pos];
        str[pos] = tmp;   //字符替换
        char *p = str + i;
        char *q = str + (slen - 1);
        Reverse(p,q);  //将替换点后的字符串逆置
        return true;    //下一个
    }
}

int qsortcmp(const void * pa,const void * pb)   //比较函数
{
    return *(char*)pa - *(char*)pb; //先强制类型转化,再取值
}

void permutation3(char* str)    //全排列的去重非递归算法
{
    qsort(str,strlen(str),sizeof(char),qsortcmp);   //快速排序
    do
    {
        for(int i = 0; i < strlen(str); i++)  //输出一个排列
            cout << str[i];
        cout << endl;
    }while(next_permutation3(str));
}

4、源代码

#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;

/*
算法思路:全排列可以看做固定前i位,对第i+1位之后的再进行全排列,比如固定第一位,后面跟着n-1位的全排列。那么解决n-1位元素的全排列就能解决n位元素的全排列了,这样的设计很容易就能用递归实现。
*/

void permutation1(char* str,int sbegin,int send)    //全排列的非去重递归算法
{
    if( sbegin == send) //当 sbegin = send时输出
    {
        for(int i = 0;i <= send; i++)   //输出一个排列
            cout << str[i];
        cout << endl;
    }
    else
    {
        for(int i = sbegin; i <= send; i++) //循环实现交换和sbegin + 1之后的全排列
        {
            swap(str[i],str[sbegin]);   //把第i个和第sbegin进行交换
            permutation1(str,sbegin + 1,send);
            swap(str[i],str[sbegin]);   //交换回来
        }
    }
}

/*
    序列有时候可能有重复的字符,当需要输出去重全排列时,就可以采取以下方法
        当第i位与i+n位交换时,i到i+n位中不能有与i+n位相同的字符
        比如说23560678,当第三位5与第六位6交换时,中间不能有6,这里有6所以不进行交换
*/

bool comparesub(char* str,int sbegin,int send)  //比较函数,[sbegin,send)中是否有与send的值相等得数
{
    for(int i = sbegin; i < send; i++)
        if(str[i] == str[send])
            return false;
    return true;
}

void permutation2(char* str,int sbegin,int send)    //全排列的去重递归算法
{
    if(sbegin == send)  //当 sbegin = send时输出
    {
        for(int i = 0; i <= send; i++)  //输出一个排列
            cout << str[i];
        cout << endl;
    }
    else
    {
        for(int i = sbegin; i <= send; i++)
        {
            if(comparesub(str,sbegin,i))    //如果sbeing到i没有重复数字
            {
                swap(str[i],str[sbegin]);   //把第i个和第sbegin进行交换
                permutation2(str,sbegin + 1,send);
                swap(str[i],str[sbegin]);   //交换回来
            }
        }
    }
}

/*
    算法思路:先排序得到递增序列,从字符串尾部向前找第一双相邻的递增字符,称前一个数为替换字符,替换字符的下标称为替换点,再从该字符后面找到一个比替换字符大的最小的字符(因为有递增关系,所以这个数必然存在的),然后再将替换点后的字符串逆置。循环到最大排序后,逆置并结束算法
    例:字符串2568710 先排序得到0125678找到一双相邻递增字符,78,再在7后面的字符里找到符合算法的字符8,替换得到字符串0125687,再将7后面的字符串逆置,得到0125687
*/

void Reverse(char* a,char* b)   //逆置函数
{
    while(a < b)
    {
        char tmp = *a;
        *a = *b;
        *b = tmp;
        a++;
        b--;
    }
}

bool next_permutation3(char* str)   //找到一个满足算法的序列
{
    int i;
    int slen = strlen(str); //str长度
    for(i = slen - 1; i >= 1; i--)  //循环找出一双相邻递增字符
    {
        if(str[i - 1] < str[i])
            break;
    }
    if(!i)  //如果i=0,证明没有一双相邻递增字符,那么也就说明整个字符串是最大排列
    {
        return false;   //结束算法
    }
    else
    {
        char tmp = str[i - 1];  //替换字符
        int pos = i;            //比替换字符大的最小的字符位置
        for(int j = i; j < slen; j++)
        {
            if(str[j] > tmp && str[j] <= str[pos])  //从替换字符后面找到一个比替换字符大的最小的字符
                pos = j;
        }
        str[i - 1] = str[pos];
        str[pos] = tmp;   //字符替换
        char *p = str + i;
        char *q = str + (slen - 1);
        Reverse(p,q);  //将替换点后的字符串逆置
        return true;    //下一个
    }
}

int qsortcmp(const void * pa,const void * pb)   //比较函数
{
    return *(char*)pa - *(char*)pb; //先强制类型转化,再取值
}

void permutation3(char* str)    //全排列的去重非递归算法
{
    qsort(str,strlen(str),sizeof(char),qsortcmp);   //快速排序
    do
    {
        for(int i = 0; i < strlen(str); i++)  //输出一个排列
            cout << str[i];
        cout << endl;
    }while(next_permutation3(str));
}
int main()
{
    char str[] = "1223";
//测试数据
//    permutation1(str,0,strlen(str) - 1);
//    permutation2(str,0,strlen(str) - 1);
    permutation3(str);
    return 0;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-07-30 15:13:24

三种全排序算法详解的相关文章

九种经典排序算法详解(冒泡排序,插入排序,选择排序,快速排序,归并排序,堆排序,计数排序,桶排序,基数排序)

综述 最近复习了各种排序算法,记录了一下学习总结和心得,希望对大家能有所帮助.本文介绍了冒泡排序.插入排序.选择排序.快速排序.归并排序.堆排序.计数排序.桶排序.基数排序9种经典的排序算法.针对每种排序算法分析了算法的主要思路,每个算法都附上了伪代码和C++实现. 算法分类 原地排序(in-place):没有使用辅助数据结构来存储中间结果的排序**算法. 非原地排序(not-in-place / out-of-place):使用了辅助数据结构来存储中间结果的排序算法 稳定排序:数列值(key)

LVS负载均衡群集(三种工作模式原理详解)

LVS负载均衡群集(三种工作模式原理详解) 一.前言 ? 在互联网应用中,随着站点对硬件性能.响应速度.服务稳定性.数据可靠性等要求越来越高,单台服务器力不从心.所以我们需要通过一些方法来解决这样的瓶颈. ? 最简单的方法就是使用价格昂贵的大.小型的主机:但这样在大多数企业中显然是不可取或者说不现实的.那么我们就需要通过多个普通服务器构建服务器群集. 二.相关概念概述 2.1何为LVS? ? LVS--Linux Virtual Server,即Linux虚拟服务器(虚拟主机.共享主机),虚拟主

算法复杂度,及三种主要排序算法的研究

一.时间复杂度 1.时间频度  T(n),n为问题的规模 即--算法中语句的执行次数.又叫语句频度. 2.时间复杂度 记作 O( f(n) ),这里的f(n)是一个T(n)的同数量级函数. 如O(1)表示算法的语句执行次数为一个常数,不随规模n的增长而增长: 又如T(n)=n^2+3n+4与T(n)=4n^2+2n+1它们的频度不同, 但时间复杂度相同,都为O(n^2). 3.算法的性能 主要用算法的 时间复杂度 的数量级来评价一个算法的时间性能. 二.空间复杂度 S(n),包括3方面: 1.算

算法:三种简单排序算法

排序算法比较常见的有:冒泡排序.简单选择排序.直接插入排序:希尔排序.堆排序.归并排序和快速排序算法等.今天先学习一下前面三种比较简单的算法.排序的相关概念: ①排序的稳定性:两个或多个元素相等,排序过后仍然是原来的顺序则为稳定排序. ②内部排序:排序过程都在内存中进行:外部排序:需要对外存进行访问的排序过程. ③内排序算法性能因素:1.时间性能,比较与移动:2.辅助空间:3.算法复杂性 实例:冒泡排序.简单选择排序与直接插入排序 #include "stdio.h" #define

13种排序算法详解

0.前言 从这一部分开始直接切入我们计算机互联网笔试面试中的重头戏算法了,初始的想法是找一条主线,比如数据结构或者解题思路方法,将博主见过做过整理过的算法题逐个分析一遍(博主当年自己学算法就是用这种比较笨的刷题学的,囧),不过又想了想,算法这东西,博主自己学的过程中一直深感,基础还是非常重要的,很多难题是基础类数据结构和题目的思想综合发散而来.比如说作为最基本的排序算法就种类很多,而事实上笔试面试过程中发现掌握的程度很一般,有很多题目,包括很多算法难题,其母题或者基本思想就是基于这些经典算法的,

js十大排序算法详解

十大经典算法导图  图片名词解释:n: 数据规模k:"桶"的个数In-place: 占用常数内存,不占用额外内存Out-place: 占用额外内存 1.冒泡排序 1.1  原始人冒泡排序 function bubbleSort(arr) { var len = arr.length; for (var i = 0; i < len; i++) { for (var j = 0; j < len - 1 - i; j++) { if (arr[j] > arr[j+1]

排序算法详解(Go语言实现):冒泡排序/选择排序/快速排序/插入排序

算法是程序的灵魂,而排序算法则是一种最基本的算法.排序算法有许多种,本文介绍4中排序算法:冒泡排序,选择排序,快速排序和插入排序,以从小到大为例. 一.冒泡排序 冒泡排序的原理是,对给定的数组进行多次遍历,每次均比较相邻的两个数,如果前一个比后一个大,则交换这两个数.经过第一次遍历之后,最大的数就在最右侧了:第二次遍历之后,第二大的数就在右数第二个位置了:以此类推. //冒泡排序(排序10000个随机整数,用时约145ms) func bubbleSort(nums []int) { for i

Java各种排序算法详解

排序大的分类可以分为两种:内排序和外排序.在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序.下面讲的排序都是属于内排序. 内排序有可以分为以下几类: (1).插入排序:直接插入排序.二分法插入排序.希尔排序. (2).选择排序:简单选择排序.堆排序. (3).交换排序:冒泡排序.快速排序. (4).归并排序 (5).基数排序 一.插入排序 ?思想:每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置,直到全部插入排序完为止. ?关键问

Javascript九大排序算法详解

排序很多时候都会用到,而在js中排序的算法有九个是人们常用的,而且使用起来可以很流畅.本文将对这九种排序算法进行详细介绍,教程尚硅谷JavaScript DOM视频教程还有详细的代码分享哦. 一.插入排序 1)算法简介 插 入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法.它的工作原理是 通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入.插入排序在实现上,通常采用in-place排序(即只需用到 O(1)的额外空间的排序),因而在从后向前扫