C语言-第五章-加餐:冒泡排序与二维数组

传送门:C语言-第五章:指针与数组

第一节:冒泡排序

        如果定义并初始化了以下的一个数组:

int arr[] = {5,1,2,7,6,8,4};

        它的元素顺序是乱的,要让它按照大小顺序排列,就可以使用冒泡排序算法。

        冒泡排序是一种简单的算法,它的原理如下:

        这样通过一步一步的比较,就可以找到最大的数并把它放到数组的最后。

        上述图片中的过程叫做一趟冒泡。

        我们先编写代码实现上述一趟冒泡的过程:

#include <stdio.h>
int main()
{
	int arr[] = { 5,1,2,7,6,4,8 };

	printf("原数组顺序:");
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ",arr[i]);
	}

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0])-1; i++)
	{
		if (arr[i] > arr[i + 1])
		{
			int tmp = arr[i];    // 需要一个中间变量存储arr[i]的值
			arr[i] = arr[i + 1]; // 因为这一步会覆盖arr[i]原来的值
			arr[i + 1] = tmp;
		}
	}

	printf("\n现数组顺序:");
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 

        一趟冒泡可以排好一个元素,n个元素不就需要n趟冒泡吗,我们给它加上循环实现n趟冒泡:

#include <stdio.h>
int main()
{
	int arr[] = { 5,1,2,7,6,4,8 };

	printf("原数组顺序:");
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ",arr[i]);
	}

	for (int j = 0; j < sizeof(arr) / sizeof(arr[0]); j++) // 冒泡趟数
	{
		for (int i = 0; i < sizeof(arr) / sizeof(arr[0]) - 1; i++)
		{
			if (arr[i] > arr[i + 1])
			{
				int tmp = arr[i];    // 需要一个中间变量存储arr[i]的值
				arr[i] = arr[i + 1]; // 因为这一步会覆盖arr[i]原来的值
				arr[i + 1] = tmp;
			}
		}
	}

	printf("\n现数组顺序:");
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

        ps:如果需要降序排列,只需把"arr[i] > arr[i+1]"改为"arr[i] < arr[i+1]"即可

        这样数组就全部排好了,但是上述代码还有3个优化点:

        第一点:

        在进行第一趟冒泡时,最大值8的位置就已经固定了:

        那我们就不需要考虑8了,所以我们实际上需要再次排序的数组是:

        如果再进行一趟冒泡,最大的7又可以不考虑了,以此类推,每进行一趟冒泡,内层循环的次数就可以-1。

        优化的代码如下:

#include <stdio.h>
int main()
{
	int arr[] = { 5,1,2,7,6,4,8 };

	printf("原数组顺序:");
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ",arr[i]);
	}

	for (int j = 0; j < sizeof(arr) / sizeof(arr[0]); j++) // 冒泡趟数
	{
		for (int i = 0; i < sizeof(arr)/sizeof(arr[0])-1-j; i++) // 每循环一次j的值就会+1
		{
			if (arr[i] > arr[i + 1])
			{
				int tmp = arr[i];    // 需要一个中间变量存储arr[i]的值
				arr[i] = arr[i + 1]; // 因为这一步会覆盖arr[i]原来的值
				arr[i + 1] = tmp;
			}
		}
	}

	printf("\n现数组顺序:");
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

        第二点:

        上述代码的冒泡排序是把最大值一个一个的从数组最后向前排好,那么完成倒数第二次排序后,上述数组的情况为:

        上述7个数,第6次冒泡后就已经全部排序好了,这是因为6个数的位置都排好了,那么最后一个数只能在它应该在的位置了,不需要再进行冒泡了。

        所以把外层循环的次数(即冒泡的趟数)减少1:

#include <stdio.h>
int main()
{
	int arr[] = { 5,1,2,7,6,4,8 };

	printf("原数组顺序:");
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ",arr[i]);
	}

	for (int j = 0; j < sizeof(arr) / sizeof(arr[0])-1; j++) // 冒泡趟数减少1
	{
		for (int i = 0; i < sizeof(arr)/sizeof(arr[0])-1-j; i++) // 每循环一次j的值就会+1
		{
			if (arr[i] > arr[i + 1])
			{
				int tmp = arr[i];    // 需要一个中间变量存储arr[i]的值
				arr[i] = arr[i + 1]; // 因为这一步会覆盖arr[i]原来的值
				arr[i + 1] = tmp;
			}
		}
	}

	printf("\n现数组顺序:");
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

        第三点:

        上述代码遍历了两次数组,代码有重复的部分,一点都不够优雅,我们可以把遍历数组和冒泡排序封装成两个函数:

        1、根据函数的作用域,我们用一个文件来专门存放函数:

        function.c 用来存放函数的定义和实现,

        main.c 用来调用函数。

        2、自定义一个名为 TraArray 的函数,一个参数是数组,另一个参数是数组的大小:

// function.c
#include <stdio.h>
void TraArray(int* arr,int size) // 因为数组名是首元素地址,所以用指针接收
{
	for (int i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
}

        那么为什么我们在函数中不用 sizeof(arr)/sizeof(arr[0]) 计算数组的大小,还需要传入数组大小呢?

        这是因为arr 数组的名字 arr 存放的是首元素地址,相当于一个指针,传入函数时也只能被作为一个指针接收,所以在函数中用 sizeof(arr) 计算的就是指针的大小(64位平台上为8字节),而不是整个数组的大小。

        函数的参数还可以这样写:

// function.c
#include <stdio.h>
void TraArray(int arr[], int size)
{
	for (int i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

        这样可以给阅读者强调它是一个数组,但是它依然被函数视为指针 。

        3、把冒泡排序自定义成一个函数,需要的参数和 TraArray 相同:

// function.c
void BubbleSort(int* arr, int size)
{
	for (int j = 0; j < size; j++)
	{
		for (int i = 0; i < size - 1-j; i++)
		{
			if (arr[i] > arr[i + 1])
			{
				int tmp = arr[i];    // 需要一个中间变量存储arr[i]的值,
				arr[i] = arr[i + 1]; // 因为这一步会覆盖arr[i]原来的值
				arr[i + 1] = tmp;
			}
		}
	}
}

        

        4、还可以把 TraArray 和 BubbleSort 再定义成一个函数:

// function.c
void TraAndBub(int* arr,int size)
{
	printf("原数组顺序:");
	TraArray(arr, size);    // 遍历并打印数组

	BubbleSort(arr, size);  // 冒泡排序

	printf("现数组顺序:");
	TraArray(arr, size);    // 遍历并打印数组
}

        

        function.c 完整的代码为:

// function.c
#include <stdio.h>
void TraArray(int* arr, int size) // 因为数组名是首元素地址,所以用指针接收
{
	for (int i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void BubbleSort(int* arr, int size)
{
	for (int j = 0; j < size; j++)
	{
		for (int i = 0; i < size - 1-j; i++)
		{
			if (arr[i] > arr[i + 1])
			{
				int tmp = arr[i];    // 需要一个中间变量存储arr[i]的值
				arr[i] = arr[i + 1]; // 因为这一步会覆盖arr[i]原来的值
				arr[i + 1] = tmp;
			}
		}
	}
}

void TraAndBub(int* arr,int size)
{
	printf("原数组顺序:");
	TraArray(arr, size);    // 遍历并打印数组

	BubbleSort(arr, size);  // 冒泡排序

	printf("现数组顺序:");
	TraArray(arr, size);    // 遍历并打印数组
}

        用函数有什么好处呢?这样做的话我们给多个数组排序就非常简单了:

// main.c
#include <stdio.h>
int main()
{
	int arr_1[] = { 5,1,2,7,6,4,8 };
	int arr_2[] = { 1,3,1,5,2 };
	int arr_3[] = { 12,33,44,11,22,43,10};
	int arr_4[] = {101,104,103,102,200};

	TraAndBub(arr_1, sizeof(arr_1) / sizeof(arr_1[0]));
    printf("\n");
	TraAndBub(arr_2, sizeof(arr_2) / sizeof(arr_2[0]));
    printf("\n");
	TraAndBub(arr_3, sizeof(arr_3) / sizeof(arr_3[0]));
    printf("\n");
	TraAndBub(arr_4, sizeof(arr_4) / sizeof(arr_4[0]));

	return 0;
}

        如果不用函数,那么每个数组都需要写很多行重复的代码。

        综上所述,函数不仅具有解耦的作用,还可以让代码更美观,最重要的是避免重复写代码。

第二节:二维数组

        2-1基本认识

        上一期我们说到:数组是用来存放相同类型数据的一种容器。

        那么能不能用数组来存放数组呢?可以!

        曾经我们讲的数组都是一维数组,而存放一维数组的数组就叫做二维数组,它的定义格式如下:

[一维数组的元素类型] [二维数组名][一维数组个数][一维数组的元素个数];
// 例如:
int Td_arr[5][6];
//表示一个二维数组,它可以存放5个一维数组,每个一位数组可以存放6个元素

        2-2.二维数组的初始化

        二维数组也有完全初始化和不完全初始化:

        1、完全初始化

int Td_arr[3][4] = {{1,2,3,4},{4,5,6,7},{7,8,9,10}};// 每个一维数组也需要用{}初始化
// 上面还可以写成:
int Td_arr[3][4] = {{1,2,3,4},
                    {4,5,6,7},
                    {7,8,9,10}};
// 这样二维数组就变成了平面,而且是3行4列,与[]中的数字呼应

// 二维数组定义时只能省略 行,即一维数组数
int Td_arr[][4] = {{1,2,3,4},
                   {4,5,6,7},
                   {7,8,9,10}};
// 可以这样理解:其实与一维数组一样,都是省略了元素个数,
// 数组的定义是存储 相同类型 的数据,
// 而一维数组的类型包括一维数组的元素类型和元素个数,
// 所以 列,即一维数组元素个数不能省略。

        2、不完全初始化

int Td_arr[3][4] = {{1,2,3,4},{4,5,6}}; // 剩下的位置为0

        2-3.二维数组的存储

        和一维数组一样,二维数组的元素(即一维数组)存储也是连续的:

        例如一个3行4列的二维数组

         2-4.二维数组名

        我们知道,数组名其实是一个指针,类型与元素类型相同,它指向数组的第一个元素,二维数组的第一个元素是一个一维数组,所以二维数组名的权限是一个一维数组,可以用偏移量证明:

#include <stdio.h>
int main()
{
	int Td_arr[3][4] = {{1,2,3,4},
						{4,5,6,7},
						{7,8,9,10}};
	printf("%p\n", Td_arr);
	printf("%p\n", Td_arr + 1); // 增加1的偏移量
	return 0;
}

        差值为16,正好是一个一维数组的大小。

        &二维数组名 和 sizeof(二维数组名) 代表整个二维数组,这里就不在演示了。

        2-5.二维数组元素的使用

        二维数组的元素就是一维数组,和一维数组一样,如果要访问到它的元素,也可以使用下标和[ ]操作符:

#include <stdio.h>
int main()
{
	int Td_arr[3][4] = {{1,2,3,4},
						{4,5,6,7},
						{7,8,9,10}};

    Td_arr[0] = {2,3,4,5}; // 修改第一个一维数组

	return 0;
}

        但是我们拿到整个一维数组是没有用的,因为我们的目的还是使用一维数组里的元素,所以还需要一个[ ]来访问一维数组里的元素:

#include <stdio.h>
int main()
{
    int Td_arr[3][4] = {{1,2,3,4},
                        {4,5,6,7},
                        {7,8,9,10}};

    // 打印第一个一维数组的元素
    printf("%d ", Td_arr[0][0]);
    printf("%d ", Td_arr[0][1]);
    printf("%d ", Td_arr[0][2]);
    printf("%d\n",Td_arr[0][3]);
    return 0;
}

        其他一维数组可以自己打印试试看。

        2-6.二维数组的遍历

        因为二维数组本质就是数组嵌套数组,所以可以使用循环嵌套的方式打印:

#include <stdio.h>
int main()
{
    int Td_arr[3][4] = { {1,2,3,4},
                        {4,5,6,7},
                        {7,8,9,10} };

    for (int i = 0; i < sizeof(Td_arr) / sizeof(Td_arr[0]); i++)    // 二维数组的一维数组个数
    {
        for (int j = 0; j < sizeof(Td_arr[0]) / sizeof(Td_arr[0][0]); j++)// 一维数组的元素个数
        {
            printf("%d ", Td_arr[i][j]);
        }
        printf("\n"); // 打印完一个一维数组就换行
    }
    return 0;
}

        

        又因为二维数组是连续存储的,所以可以用指针+偏移量的方式打印:

#include <stdio.h>
int main()
{
    int Td_arr[3][4] = { {1,2,3,4},
                        {4,5,6,7},
                        {7,8,9,10} };
    int* ptr = (int*)Td_arr; 
    // 因为数组名存放的是首元素的地址,
    // 所以二维数组名存放了一维数组的地址,
    // 一维数组名又存放的是首个 int 的地址
    // 即二维数组名存放的地址就是 首个 int 的地址,
    // 但是它们的权限不同,即类型不同,所以要用显示类型转换

    for (int i = 0;i < sizeof(Td_arr) / sizeof(Td_arr[0][0]); i++) // 二维数组中所有 int 类型数据的数量
    {
        printf("%d ", *(ptr + i));
    }
    printf("\n");
    return 0;
}

下期预告:

        下一期是综合案例,我们将使用之前所学的所有知识写一个扫雷小游戏

  • 16
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值