行列式计算
在线性代数中,我们接触到了行列式的定义及相关计算,现在我们可以用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 列元素