专栏:算法
下一篇:(二)基础运算
1. 数组
C/C++中的数组能够顺序存储相同类型的多个数据。除了存储数据,我们也希望能够访问数据。访问数组中的某个元素的方法就是将其编号然后 索引。
C/C++中元素的编号是从0开始,而不是从1开始,遵循左闭右开原则。如果我们有
N
N
N个元素,它们的编号则为
0
0
0 到
N
−
1
N-1
N−1。这样,对于一个数组 a,我们就能把
0
0
0 到
N
−
1
N-1
N−1 之间任意的i作为元素的索引,在代码中用a[i]唯一地表示第
i
+
1
i+1
i+1个元素的值,在 C/C++ 中这种数组被称为一维数组。
1.1 数组的创建
在C/C++中,创建数组有两种方式:
第一种方式是直接创建一个固定大小的数组,它所需要的内存在 编译期 就会被确定和分配。声明数组时,必须要用一个编译时常量指定数组的长度。
C99标准中的 变长数组(Variable-Length Array,简称 VLA) 允许使用变量来指定数组的长度,但是C++标准并不支持,GNU C编译器扩展语法中支持此语法。
int a[16];
第二种方式是使用 动态内存分配,可以在 运行期 分配内存,所需的内存空间大小不需要在编译期确定。
C函数库<stdlib.h>中提供了两个函数:malloc() 和 free(),分别用于执行动态内存的分配和释放。
#include <stdlib.h>
int* a = (int*)malloc( 16 * sizeof(int));
free(a);
而C++中有分别用于动态内存分配和释放的关键字:new 和delete。
int* a = new int[16];
delete[] a;
当动态分配的内存不再需要使用时,应该将其释放,将其归还给系统内存池,这样它以后可以被重新分配使用。分配内存但在使用完毕后不释放将引起内存泄漏(memory leak)。
1.2 数组的初始化
在上面的示例中,数组创建后并没有被初始化,那么数组元素的值是多少呢?
如果数组在代码块之外被声明或者使用了static关键字进行声明,那么数组将被存储于静态内存中,也就是不属于栈的内存,这时数组属于静态变量,数组元素的初始值将会自动设置为零。
如果数组在代码块内部声明,且没有用static关键字声明为静态变量,那么数组将被存储在栈中,属于自动变量,此时数组中元素的值是未初始化的,元素的值是多少取决于内存原本的值。
就像普通变量可以在声明中进行初始化一样,数组也可以这样做。唯一的区别就是数组的初始化需要一系列的值,这些值位于一对花括号内,每个值之间用逗号分隔。初始化列表给出的值将被逐个赋值给数组的各个元素。
int a[6] = {3, 1, 4, 1, 5, 9};
需要注意的是,声明的数组长度可以比列表长度大,但不能比列表长度小。
int a[3] = {1, 2, 3}; //正确
int a[4] = {1, 2, 3}; //正确,声明的数组长度可以大于列表长度,剩余的元素将被初始化为默认值0
int a[2] = {1, 2, 3}; //错误,声明的数组长度不允许小于列表长度
如果声明中并未给出数组的长度,编译器就把数组的长度设置为刚好能够容纳列表中所有初始值的长度。如果初始值列表经常修改,这个技巧尤其有用。
如果声明中并未给出数组的长度,该如何得知呢?可以使用 sizeof 关键字。
int a[] = {1, 2, 3}; //正确,数组长度不写明,将和列表长度一致,长度为3
int length = sizeof(a) / sizeof(a[0]); //获取数组a的长度
1.3 多维数组
如果某个数组的维数不止一个,那么它将被称为多维数组。例如,下面的声明:
int a[2][3]; //二维数组
int b[10][16][2]; //三维数组
int c[2][3][4][5]; //四维数组
多维数组的内存依然是连续的。多维数组中最常用的是二维数组,是“数组的数组”。
下面来看看一维数组a[3]和二维数组b[2][3]的数组元素的存储顺序(storage order):
在上图中,二维数组b[2][3]优先存储b[0],再存储b[1],展示了数组元素的存储顺序。
在C中,多维数组的元素存储按照最右边的下标率先变化的原则,称为行主序(row major order)。
二维数组的动态内存分配则需要先分配一个指针数组,再为数组里的每个指针元素赋值一个一维数组的首地址。释放时顺序则相反,先释放指针数组里指针所指的内存,再释放指针数组的内存。
int row = 2, col = 3;
//分配内存
//先动态分配一个int*型的指针数组
int** a = new int*[row];
//再为数组里的指针素动态分配一个数组
for (int i = 0; i < row; i++) {
a[i] = new int[col];
}
//释放内存
//释放时释放顺序则相反,先释放指针数组里指针所指的内存
for (int i = 0; i < row; i++) {
delete[] a[i];
}
//再释放指针数组的内存
delete[] a;
2. 基础算法
2.1 数的运算
2.1.1 交换两个数
通常使用三变量法交换两个变量的值,对于 int 型变量 a 和 b ,可以使用如下代码进行交换:
int temp = a;
a = b;
b = temp;
写成函数形式:
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
swap() 函数调用:
swap(&a, &b);
2.1.2 求两数中的较大值或较小值
可以使用三目运算符的简洁写法,比较两个数值,如果 a > b a > b a>b,则返回 a a a,否则返回 b 。 b。 b。
int max(int a, int b)
{
return a > b ? a : b;
}
int min(int a, int b)
{
return a < b ? a : b;
}
因为上述的函数十分简单,通常定义为宏或者内联来避免函数调用耗时(当然,编译器优化可能会自动帮我们进行这项操作)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
inline int max(int a, int b) { return (a > b) ? a : b; }
inline int min(int a, int b) { return (a < b) ? a : b; }
2.1.3 求两个数的最大公因数
最大公因数(Greatest common divisor)也叫做最大公约数,两个整数的最大公因数(Gcd)是能同时整数二者的最大整数。例如 Gcd(50, 15) = 5,Gcd(16, 8) = 8。
求解通常使用欧几里得算法,也叫辗转相除法。做法是求
m
m
m,
n
n
n两个数的余数
r
r
r,然后令
m
=
n
m=n
m=n,
n
=
r
n=r
n=r,再重复操作,直至余数
m
%
n
=
0
m\%n=0
m%n=0,此时除数
n
n
n即为最大公因数。
unsigned int gcd(unsigned int m, unsigned int n)
{
unsigned int rem;
while (n > 0) {
rem = m % n; //求m 除以 n 的余数
m = n;
n = rem;
}
return m;
}
这里无需保证 m ⩾ n m \geqslant n m⩾n,因为如果 m < n m < n m<n,那么余数为 m m m ,第一次循环将使他们交换。
2.1.4 求取两个数的最小公倍数
两个数的最小公倍数(Lowest common multiple)是指能同时被两个数整除的整数中的最小值,有如下计算公式。
两
数
的
最
小
公
倍
数
=
两
数
的
乘
积
两
数
的
最
大
公
因
数
两数的最小公倍数 = \cfrac{两数的乘积}{两数的最大公因数}
两数的最小公倍数=两数的最大公因数两数的乘积 鉴于分母不能为0,所以需要额外加个判断,防止两数都为0时出错。
unsigned int gcd(unsigned int m, unsigned int n);
unsigned int lcm(unsigned int a, unsigned int b)
{
unsigned gcdValue = gcd(a, b);
return (gcdValue != 0) ? (a * b / gcdValue) : 0;
}
2.1.5 判断一个数是否是质数
质数(Prime)又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数,否则称为合数。
规定1既不是质数也不是合数。最小的质数是2,最小的合数是4。
由定义,判断一个数是否是质数,可以检查这个数能否被从 2 2 2 到 N − 1 N-1 N−1 之间的某个整数整除即可。同时考虑到,如果一个数可以被一个数整数,那就能写成 c = a ⋅ b c =a \cdot b c=a⋅b 的形式, a a a 或 b b b 总有一个不大于 c \sqrt{c} c(如果两个都大于 c \sqrt{c} c,那么两者的乘积就不可能等于 c c c),所以只需判断到 c \sqrt{c} c即可。
#include <math.h>
bool isPrime(int n)
{
//小于等于1的数都不是质数
if (n <= 1)
return false;
// 计算n的平方根,近似取整
int sqrtn = (int)round(sqrt(n));
//对2 ~ sqrt(n)之间的数进行遍历,分别求余数,存在一个余数不为0则不是质数
for (int i = 2; i <= sqrtn; i++) {
//如果能被其中一个整数,则不是质数
if ( n % i == 0)
return false;
}
//不能被2~ sqrt(n)的任何数整除,则为质数
return true;
}
2.2 典型的数组处理
2.2.1 遍历数组
遍历数组是指按照某种顺序,对数组中的所有元素都进行一次访问,如赋值,输出等。
下面是常见的一维数组和二维数组的遍历输出。
int a[10] = {0};
int b[5][6] = {0};
for (int i = 0; i < 10; i++) {
printf("%d ", a[i]);
}
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 6; j++) {
printf("%d ", b[i][j]);
}
printf("\n");
}
为了避免重复编写遍历数组的代码,我们定义一个遍历输出一维数组的 traverse() 函数:
void traverse(int a[], int length)
{
for (int i = 0; i < length; i++)
printf("%d ", a[i]);
printf("\n");
}
有时我们并不是希望输出值,而是进行其它的操作,则可以传入一个函数指针,在遍历时调用。在使用时,先定义一个如 void func(int* x)
之类的函数,在函数中对值进行处理,调用时将函数名传入参数中,如:traverse(a, length, func)
void traverse(int a[], int length, void (*func)(int*))
{
for (int i = 0; i < length; i++)
func(&a[i]);
}
对于二维数组,则可以通过双重循环来遍历。
void traverse(int** a, int row, int col, void (*func)(int*))
{
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
func(&a[i][j]);
}
调用方式如下:
#include <stdio.h>
//先定义函数
void printValue(int* x)
{
//访问或修改
printf("%d ", *x);
}
int a[10] = {0};
int b[3][5] = {0};
//调用
traverse(a, 10, printValue);
traverse(b, 3, 5, printValue);
2.2.2 找出数组中最大值或最小值
要求
找出数组中的最大值(最小值),返回其索引。
声明一个变量maxValue来记录数组中元素的最大值,遍历数组,与每一个元素进行比较,当元素的值比当前记录的最大值还要大时,更新maxValue的值。
int max(const int a[], int length)
{
int maxValue = a[0];
for (int i = 1; i < length; i++) {
if (a[i] > maxValue)
maxValue = a[i];
}
return maxValue;
}
最小值则遍历比较,找到更小的值时进行更新。
int min(const int a[], int length)
{
int minValue = a[0];
for (int i = 1; i < length; i++) {
if (a[i] < minValue)
minValue = a[i];
}
return minValue;
}
2.2.3 计算数组元素的平均值
要求
计算数组元素的平均值并返回,当数组长度为0时,返回0。
遍历数组,对数组进行求和,最后判断数组长度是否大于0,数组长度大于0则返回平均值(数组所有元素值之和除以数组长度),否则返回0。
double average(const int a[], int length)
{
double sum = 0.0;
for (int i = 0; i < length; i++)
sum += a[i];
return (length > 0) ? (sum / length) : 0;
}
2.2.4 复制数组
要求
创建一个和给定数组大小一致且对应元素值都相等的数组,返回其首地址。
动态分配一个相同长度的数组,然后逐一复制每个数组元素。
int* copy(const int a[], int length)
{
int* b = new int[length];
for (int i = 0; i < length; i++)
b[i] = a[i];
return b;
}
如果仅要求将元素复制到另一个数组中,则只需要将数组逐一复制即可。
void copy(int dest[], const int src[], int length)
{
for (int i = 0; i < length; i++)
dest[i] = src[i];
}
2.2.5 数组元素逆序
要求
将数组中元素的顺序颠倒,原来位置为顺数第 n n n个的元素变成倒数第 n n n个。
将数组对应位置上的元素成对交换即可。假设数组长度为 N N N,交换的两个元素索引 i , j i, j i,j满足关系: i + j = N − 1 i+j=N-1 i+j=N−1。
void inverse(int a[], int length)
{
for (int i = 0; i < length / 2; i++) {
int temp = a[i];
a[i] = a[length - 1 - i];
a[length - 1 - i] = temp;
}
}
2.2.6 在数组中查找值
要求
在数组中查找出给定值相等的元素,返回其索引,如果数组中有多个元素的值和给定值相等,则返回最小的一个索引。如果数组中没有和给定值相等的元素,返回-1。
从头开始遍历数组,直到查找到和给定值相等的元素为止,返回其索引。如果遍历完成后仍未查找到,则返回-1。这种查找方法也叫线性查找。
int search(int value, const int a[], int length)
{
int pos = 0;
while ((pos < length) && (a[pos] != value)) {
pos++;
}
return (pos >= length) ? -1 : pos;
}
2.2.7 判断数组是否有序
2.2.7.1 数组升序
要求
判断数组中的元素是否是按照从小到大的顺序排序(单调递增)。
数组升序即数组中任意两个元素都满足索引值较大的元素大于等于索引值较小的元素(即如果i > j,有a[i] >= a[j])。
注意:
这里并不要求数组元素值 严格单调递增,允许有相等的情况。
遍历数组,比较相邻的两个元素,如果找到不符合升序(索引值较大的元素反而值比较小)的情况,则可以直接判断出数组不是升序,结束循环。循环结束后,如果没有找到不符合的情况,说明数组为升序。
bool isAscending(const int a[], int length)
{
for (int i = 1; i < length; i++) {
if (a[i] < a[i - 1])
return false;
}
return true;
}
2.2.7.2 数组降序
数组降序判断也是类似的处理。
bool isDescending(const int a[], int length)
{
for (int i = 1; i < length; i++) {
if (a[i] > a[i - 1])
return false;
}
return true;
}
2.2.7.3 判断数组有序
要求
判断一个数组是否有序(单调性),升序或降序均可,元素可以相等,不要求严格单调。
数组有序要求数组是升序或者降序均可,那么我们可以从头开始遍历,先找出两个相邻且不相等的值,然后继续遍历数组,根据这两个值的大小关系来对数组剩余部分做升序判断或者降序判断。如果找不到两个不相等的值,则说明数组中所有的元素都相等,数组有序。
//这里需要调用上面的两个函数
bool isAscending(const int a[], int length);
bool isDescending(const int a[], int length);
bool isSorted(const int a[], int length)
{
int i = 1;
while ((i < length) && (a[i-1] == a[i])) {
i++;
}
if (i < length) {
int len = length - i;
if (a[i] > a[i - 1])
return isAscending(&a[i], len);
else
return isDescending(&a[i], len);
}
else
return true;
}
2.2.8 对数组中的元素进行排序
排序是指将一系列元素通过某种方法,将其按照某种顺序排列的过程。对于数组中的值,我们可以直接根据其值的大小,按照从小到大的顺序对它们进行排列。
排序我们可以使用经典的冒泡排序(Bubble sort):从头到尾遍历数组,比较相邻两个数的大小,当遇到顺序不正确的相邻两个值则将它们交换,不断重复遍历直到数组有序。
需要遍历多少次才会使数组有序呢?
答案是对于一个长度为 n n n的数组,遍历 n − 1 n-1 n−1次即可保证数组有序。
每次遍历都能从剩余未排好序的部分筛选出一个最值放到正确的位置上,当遍历 n − 1 n-1 n−1次后, n − 1 n-1 n−1个元素都放在了正确的位置上,由于是交换操作,剩余一个自然也是在其正确位置上。
对于一个长度为 n n n的数组,只需遍历 n − 1 n-1 n−1 次即可保证数组有序,并且每次遍历后,都会一个最大或最小的元素放在其正确位置上,后续操作就无需对其进行比较,所以一次遍历需要比较的次数分别是: n − 1 , n − 2 , ⋯ , 1 n-1,n-2,\cdots, 1 n−1,n−2,⋯,1,最多需要比较 n ( n − 1 ) 2 \cfrac{n(n-1)}{2} 2n(n−1)次。
void bubbleSort(int a[], int length)
{
for (int i = length - 1; i > 0; i--) {
for (int j = 0; j < i; j++) {
if (a[j] > a[j + 1]) {
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
这里我们还可以进行优化,如果一次遍历过后,相邻的两个元素都有序,没有进行交换,这里假设是要按从小到大的顺序进行排序,即 a [ 0 ] ⩽ a [ 1 ] , a [ 1 ] ⩽ a [ 2 ] , ⋯ , a [ n − 2 ] ⩽ a [ n − 1 ] a[0] \leqslant a[1] ,a[1] \leqslant a[2], \cdots , a[n-2] \leqslant a[n-1] a[0]⩽a[1],a[1]⩽a[2],⋯,a[n−2]⩽a[n−1] 可得: a [ 0 ] ⩽ a [ 1 ] ⩽ a [ 2 ] ⩽ ⋯ ⩽ a [ n − 2 ] ⩽ a [ n − 1 ] a[0] \leqslant a[1] \leqslant a[2] \leqslant \cdots \leqslant a[n-2] \leqslant a[n-1] a[0]⩽a[1]⩽a[2]⩽⋯⩽a[n−2]⩽a[n−1]
所以,当遍历一遍后,如果所有相邻的两个元素都不需要交换,那么整个数组就是有序的。这样在数组已经排好序的情况下,不用再进行多余的遍历操作。这在数组本身只有少数几个元素被稍微移动位置的情况下可以节省较多的时间。如果元素本身是随机的,则并不能节省多少。
void bubbleSort(int a[], int length)
{
for (int i = length - 1; i > 0; i--) {
//初始没有发生元素交换
bool exchanged = false;
for (int j = 0; j < i; j++) {
if (a[j] > a[j + 1]) {
//标记发生了元素交换
exchanged = true;
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
//如果遍历后没有发生元素交换,则说明数组有序,无需再遍历
if (!exchanged)
break;
}
}
2.2.8.1 如果验证排序算法的正确性
我们该如何验证我们的排序算法是正确的呢?
对于少量元素(几十个)的情况下,我们可以直接将数组输出,通过人眼观察进行比较。
如果有大量元素,人眼观察的方法就不可行。我们可以如 上一节 判断数组是否有序 中的isAscending()
和isDescending()
函数那样,定义用于检测数组有序的函数来对排序后的数组进行检测。
2.3 数学函数
2.3.1 求一个数的绝对值 ∣ x ∣ \left| x \right| ∣x∣
判断值是否小于0,小于则返回其相反数,否则返回原来的值。
int abs(int x)
{
return (x < 0) ? -x : x;
}
double abs(double x)
{
return (x < 0.0) ? -x : x;
}
2.3.2 计算两数的平方和 a 2 + b 2 a^2+b^2 a2+b2
double square(double a, double b)
{
return a*a + b*b;
}
2.3.3 计算直角三角形的斜边
由勾股定理可得,直角三角形两条直角边 a a a和 b b b与斜边 c c c的长度关系为: c 2 = a 2 + b 2 c^2 = a^2 + b^2 c2=a2+b2,可得: c = a 2 + b 2 c = \sqrt{a^2 + b^2} c=a2+b2。
#include <math.h>
double hypotenuse(double a, double b)
{
return sqrt(a*a + b*b);
}
2.4 计时
在程序中时有计时的需要,比如计算一段代码运行的耗时,这时可以通过调用C标准库 <time.h> 中的函数来完成计时工作。
2.4.1 clock()函数
clock() 函数可以返回当前程序运行的时间,单位是(1 / CLOCKS_PER_SEC) 秒,这个宏CLOCKS_PER_SEC通常是1000,所以clock()的时间单位通常是毫秒(ms)。
函数声明:
#include <time.h> //头文件包含
clock_t clock(void);
返回值类型为 clock_t,是一个整数类型,表示程序运行时间(单位通常为毫秒)。
typedef long clock_t;
可以通过在一段代码前后分别调用 clock() 函数获取程序运行时间,两者相减即可得到代码的运行时间。
示例如下:
#include <time.h>
//记录开始时间
clock_t startClock = clock();
//执行一些代码
//记录结束时间
clock_t endClock = clock();
//计算运行时间(结束时间 - 开始时间),单位毫秒
clock_t runTime_ms = (endClock - startClock) * 1000 / CLOCKS_PER_SEC;
printf("代码运行时间为:%ld 毫秒\n", runTime_ms);
因为通常 CLOCKS_PER_SEC 为1000,所以一般计算运行时间时,也使用如下代码即可:
clock_t runTime_ms = endClock - startClock;
2.4.2 time()函数
time() 函数通常用来获取当前时间对应的秒数,作为随机数的种子,参数传入 NULL 即可。
实际是从1970-01-01 00:00:00开始到当前时间的秒数。
函数声明:
#include <time.h>
time_t time(time_t *timer);
返回值类型是 time_t,整数类型,被定义为 long 或 long long。实际上32位是不够的,只能记录100多年,所以现在用64位的long long。
使用示例如下:
#include <time.h>
#include <stdlib.h>
//获取当前时间秒数
time_t curTime = time(NULL);
//作为随机数初始化种子
srand((unsigned int)curTime);
//可以直接简写成
srand((unsigned int)time(NULL));
2.5 随机数
2.5.1 C标准库伪随机数 rand()
伪随机数即使用用确定性的算法计算出随机数序列。这些由算法计算出的随机数序列,输入相同的参数,可以得到相同的随机数序列。好处是伪随机数序列可以复现结果,通过设置相同的输入,得到相同的序列,以此来复现程序运行的结果。
C标准库 <stdlib.h> 中有生成伪随机数的 rand() 函数,生成的随机数满足均匀分布,范围:0 ~ 65535。
rand() 函数使用前需要使用 srand() 设置一个随机数种子,算法将以此值作为计算伪随机数序列的初始值,如果不设置,那么每次生成的伪随机数序列都是相同。通常使用 time() 返回的时间值来设置初始值。但需要注意的是,**time()**的时间单位是秒,所以程序两次运行时间间隔较短时,也会得到相同的值。
srand() 只需要在程序中,rand()未使用前时调用一次即可,后续直接使用 rand() 不需要再调用 srand()。
#include <stdlib.h>
#include <time.h>
srand((unsigned int)time(NULL));
int r = rand();
2.5.2 随机赋值
对一个数组随机赋值,范围由 rand() 返回值决定。
#include <stdlib.h>
void randomAssign(int a[], int length)
{
for (int i = 0; i < length; i++)
a[i] = rand();
}
2.5.2.1 指定元素取值范围赋值
若要想指定元素取值范围: [ l o w , h i g h ) [low , high) [low,high),,可以在使用 rand() 获取随机值后,对 ( h i g h − l o w ) (high-low) (high−low) 取余数。当然,取余得到数值并非均匀分布, ( h i g h − l o w ) (high-low) (high−low) 的值越大,分布很可能就越不均匀,因为rand()能够生成随机数个数为 65536 65536 65536,并不一定能整除 ( h i g h − l o w ) (high-low) (high−low)。
#include <stdlib.h>
void randomAssign(int a[], int length, int low, int high)
{
for (int i = 0; i < length; i++)
a[i] = low + rand() % (high - low);
}
2.5.3 随机置乱算法
将一个序列随机打乱,可以使用 高纳德置乱算法(Knuth-Fisher-Yates algorithm) 。
从操作上看,相当于小球装到同一个袋子中,然后从袋子中随机取出一个小球,按取出的顺序放成一排,直至取出所有小球,这样就打乱了顺序。
对于数组来说,为了减少移动操作和空间的使用,采用原地修改的方式,用交换的方式代替取出元素的操作,这样也避免了取出元素后,需要移动之后所有元素的操作。
步骤:
1. 假设数组长度为
n
n
n,共有
n
n
n个元素。
2. 从剩余的
n
n
n元素中随机选取一个元素,和在位置
0
0
0上的元素交换。(注意,位置
0
0
0上的元素也在随机选取的范围内)
3. 位置
0
0
0上的元素已经选好,再从剩余的
n
−
1
n-1
n−1元素中随机选出一个元素,和在位置
1
1
1上的元素交换。
4. 位置
0
0
0和位置
1
1
1上的元素都已选好,再从剩余的
n
−
1
n-1
n−1个元素中随机选出一个元素,和在位置
2
2
2上的元素进行交换。
以此类推,直至选取完所有元素。
下面为对应算法的代码(下面是逆序遍历,和正序遍历主要是索引不同)
#include <stdlib.h>
void shuffle(int a[], int length)
{
for (int i = length - 1; i > 0; i--) {
//从索引为0 ~ i的元素之中随机选一个和a[i]互换
int r = rand() % (i + 1);
swap(&a[i], &a[r]);
}
}
//交换两个变量的值
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
专栏:算法
下一篇:(二)基础运算