全排列,逆序数与行列式的二三事

行列式计算

    在线性代数中,我们接触到了行列式的定义及相关计算,现在我们可以用C语言来帮助我们实现行列式的计算

    一起来把这个顽固的行列式算出来(╯‵□′)╯︵┻━┻


行列式计算方式

首先先来回顾一下行列式的计算方式

行列式由定义可知行数和列数相同。对于一个n阶行列式,其行数和列数都是n

a i j 代表行列式中第 i 行第 j 列的元素

 行列式的计算公式

其中 p1p2 ··· pn 为自然数 1, 2, ··· , n 的一个排列,t 为这个排列的逆序数

该公式代表对p1p2 ··· pn所有排列对应的若干项求和,排列数即为项数

那么我们可以将 p1p2 ··· pn 看作n个数在n个数位上进行全排列,然后再执行相应的计算

也就是说,行列式计算需要三步


全排列

在之前的排列专题文章中,已介绍了比较法,记录法,交换法三种排列方法,故这里不再多介绍。

由于行列式行数列数相等,所以排列是全排列,且只是计算就不用考虑按序输出排列,因而推荐使用较快的交换法。

逆序数

  • 比较法

    比较法顾名思义就是按照定义,依次将某数前面的数和它比较大小

#include<stdio.h>
void compare(int digit[],int n);

int cnt=0;//用于给逆序数计数 

int main()
{
	int n;
	printf("数的个数:"); 
	scanf("%d",&n);
	int digit[n];
	
	printf("数:"); 
	for(int i=0;i<n;i++) scanf("%d",&digit[i]);
	
	compare(digit,n);
	printf("逆序数:%d",cnt);
} 


void compare(int digit[],int n)
{
	for(int i=0;i<n;i++)//i是当前的数的位置 
	{
		for(int j=0;j<i;j++)//j是当前的数前面的数的位置 
		{
			if(digit[j]>digit[i]) cnt++;
		}
	}
}

运行效果图

  • 归并法

归并法利用到了归并排序进行逆序数计算。在之前排序专题的推文中我们已介绍了归并排序。

在这里我们简单回顾一下归并排序的思想。根据分治法的思想,归并排序就是“先分再合”。

先将一个数组无限地一分为二直至不能再分。然后在合并数组的过程中,将每次分割的两个子数组中的元素进行比较,根据顺序依次将元素放到新数组中。重复此步骤直至合并成原来数组的长度。

那么在合并数组过程中的数组元素比较就可以顺便计算逆序数。

#include<iostream>
using namespace std;

void mergesort(int temp_digit[],int L,int R);

int cnt=0;//用于给逆序数计数 

int main()
{
	int n;
	cout<<"排序的数的个数:";
	cin>>n;
	int temp_digit[n];
	cout<<"准备排序的数:";
	for(int i=0;i<n;i++) cin>>temp_digit[i];
	mergesort(temp_digit,0,n-1);
	cout<<"经排序过的数:";
	for(int i=0;i<n;i++) cout<<temp_digit[i]<<" ";
	cout<<endl<<"逆序数:"<<cnt; 
} 


void mergesort(int temp_digit[],int L,int R)//L,R分别为当前数组的首尾位置 
{
	if(L==R) return;//当数组只剩一个数(无法再分割时),返回 
	int middle=(L+R)/2;//将当前数组一分为二
	mergesort(temp_digit,L,middle);//对子数组同样一分为二,直到数组无法再分割 
	mergesort(temp_digit,middle+1,R);
	int temp[R-L+1];
	//临时数组,用于暂时存放排好序的数
	//达到排序并合并两子数组的效果 
	int pos1=L,pos2=middle+1,pos3=0;
	//pos1是在子数组1中的当前位置
	//pos2是在子数组2中的当前位置
	//pos3是在临时数组中的当前位置 
	while(pos1<=middle&&pos2<=R)
	{
		//两子数组中的数进行比较,较小的数放在临时数组前面 
		if(temp_digit[pos1]<=temp_digit[pos2]) 
		{
			temp[pos3++]=temp_digit[pos1++];
		}
		else
		{
			temp[pos3++]=temp_digit[pos2++];
			cnt+=middle-pos1+1;
			 //当子数组2当前位置的数比子数组1当前位置的数小时(不能有等于)
			 //子数组1中剩余的数的个数加入逆序数计数中 
		}
	} 
	while(pos1<=middle)//将比较后子数组1剩余的数放进临时数组 
	{
		temp[pos3++]=temp_digit[pos1++];
	}	
	while(pos2<=R)//将比较后子数组2剩余的数放进临时数组 
	{
		temp[pos3++]=temp_digit[pos2++];
	}

	for(int i=0;i<pos3;i++)//将临时数组排好序的数放回原数组
	{
		temp_digit[L+i]=temp[i];
	}
}

运行效果图

公式计算

结合全排列,逆序数,公式即可进行行列式计算

下面的代码以交换法实现全排列和归并法实现逆序数计算为例,这样当行列式阶数n较大时,计算速度相对较快

#include<stdio.h>

void swap(int *,int*);
void arrange(int [],int [],int [],int,int);
void mergesort(int [],int,int);

int cnt=0;//cnt用于逆序数计数
int term_cnt=0;//term_cnt用于项数计数 
int ans=0;//ans为行列式计算结果

int main()
{
	int n;
	printf("行列式阶数:");
	scanf("%d",&n);
	int digit[n];
	int temp_digit[n];//temp_digit是digit的临时副本,用于给归并法排序来算逆序数 
	int det[n*n];
	//为方便将数组传入函数,用一维数组det存储行列式元素 
	//行列式中i行j列元素为数组det中第i*n+j个元素 
	printf("\n行列式:\n");
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			scanf("%d",&det[i*n+j]);
		}
	}
	
	for(int i=1;i<=n;i++)
	{
		digit[i-1]=i;
	}

	printf("\n行列式计算式的项:\n");
	arrange(det,digit,temp_digit,n,0);
	printf("\n行列式计算式项数:%d\n\n",term_cnt);
	printf("行列式计算结果为:%d",ans);
	return 0;
}

void swap(int *x,int *y)//交换 
{
	int temp;
	temp = *x;
	*x = *y;
	*y = temp;
}

void arrange(int det[],int digit[],int temp_digit[],int n,int pos)
{
	//arrange是用交换法对a1p1 a2p2...anpn(n和pn都是下标)中的p1p2...pn进行全排列 
	if(pos==n-1)//到最后一位时,开始套公式计算行列式 
	{
		int term=1;//term重置为1 
		for(int i=0;i<n;i++)//此处循环中的i+1是aij的i,digit[i]是j(在数组中下标都减一)
		{
			term*=det[i*n+digit[i]-1];//term是当前的项 
		}
		
		//为了不改变原排列的顺序,需要用一个临时数组作为原排列的副本 	
		for(int i=0;i<n;i++)
			temp_digit[i]=digit[i];
		
		cnt=0;//cnt重置为0 
		//将临时数组传入归并法,用于临时排序的同时计算逆序数 
		mergesort(temp_digit,0,n-1);
		
		term_cnt++;//项数+1 
		if(cnt%2==1)//若逆序数为奇数,则当前项前有负号 
		{
			printf(" -");
			ans=ans-term;
		}
		else if(cnt%2==0)//若逆序数为偶数,则当前项前有正号 
		{
			printf(" +");
			ans=ans+term;
		}
		for(int i=0;i<n;i++) 
		{
			//用a(i,j)表示行列式中第i行第j列的元素
			//将每一项(包括前面的正负符号)输出,展现计算过程 
			printf(" a(%d,%d) ",i+1,digit[i]);
			if(i!=n-1) printf("*");
		}
		printf("\n");
				
	}
	else
	{
		for(int i=pos;i<n;i++)//i是当前位pos后面的位 
		{
			swap(digit+i,digit+pos);//digit[i]和digit[pos]交换 
			arrange(det,digit,temp_digit,n,pos+1);//进入到下一位的排列 
			swap(digit+i,digit+pos);//还原交换的两数 
		}
	}
}

void mergesort(int temp_digit[],int L,int R)//L,R分别为当前数组的首尾位置 
{
	if(L==R) return;//当数组只剩一个数(无法再分割时),返回 
	int middle=(L+R)/2;//将当前数组一分为二
	mergesort(temp_digit,L,middle);//对子数组同样一分为二,直到数组无法再分割 
	mergesort(temp_digit,middle+1,R);
	int temp[R-L+1];
	//临时数组,用于暂时存放排好序的数
	//达到排序并合并两子数组的效果 
	int pos1=L,pos2=middle+1,pos3=0;
	//pos1是在子数组1中的当前位置
	//pos2是在子数组2中的当前位置
	//pos3是在临时数组中的当前位置 
	while(pos1<=middle&&pos2<=R)
	{
		//两子数组中的数进行比较,较小的数放在临时数组前面 
		if(temp_digit[pos1]<=temp_digit[pos2]) 
		{
			temp[pos3++]=temp_digit[pos1++];
		}
		else
		{
			temp[pos3++]=temp_digit[pos2++];
			cnt+=middle-pos1+1;
			 //当子数组2当前位置的数比子数组1当前位置的数小时(不能有等于)
			 //逆序数计数加上子数组1中剩余的数的个数
		}
	} 
	while(pos1<=middle)//将比较后子数组1剩余的数放进临时数组 
	{
		temp[pos3++]=temp_digit[pos1++];
	}	
	while(pos2<=R)//将比较后子数组2剩余的数放进临时数组 
	{
		temp[pos3++]=temp_digit[pos2++];
	}

	for(int i=0;i<pos3;i++)//将临时数组排好序的数放回原数组 
	{
		temp_digit[L+i]=temp[i];
	}
}

运行效果图

以a(i , j)表示行列式中第 i 行第 j 列元素

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值