目录
冒泡排序详解
什么是冒泡排序呢?
它是通过多次遍历和比较,将最大(或最小)的元素逐步冒泡到最右(或最左)的位置,类似于水泡上升的过程,因此得名"冒泡"排序。
具体步骤是这样的:
- 从数组的第一个元素开始,依次比较相邻的两个元素。
- 如果前一个元素大于后一个元素,交换它们的位置,使得较大的元素排在后面。
- 继续向下遍历,对剩余的元素进行相同的比较和交换,直到达到倒数第二个元素。
- 重复以上步骤,直到整个数组排序完成,即没有发生任何交换操作。
但是这样子还是不太好理解怎么办,可以继续往下看,理解冒泡的过程:
初始状态:
5 *****
3 ***
8 ********
4 ****
2 **
第一轮遍历,比较并交换元素:(经过第一轮遍历,最长的8被冒到了最后一位)
3 ***
5 *****
4 ****
2 **
8 ********
第二轮遍历,比较并交换元素:(经过第二轮遍历,第二长的5被冒到了倒数第二位)
3 ***
4 ****
2 **
5 *****
8 ********
第三轮遍历,比较并交换元素:(第三轮是4)
3 ***
2 **
4 ****
5 *****
8 ********
最终排序完成:(最后一轮是3)
2 **
3 ***
4 ****
5 *****
8 ********
如此一来,长度为5的序列经过四轮遍历后被成功排序
那么在每一轮遍历中,经过了几次比较呢?
我们发现,第一轮遍历需要比较的次数是4次,第二轮是三次,第三轮是两次,第四轮是一次
简单总结是,遍历次数i,比数字个数n小一,也就是i= n-1;
每一轮遍历时比较的次数是未被排序的数字个数(n-i)*减一,也就相当于是i-1
*:每一次遍历过后,都有一个数字归位!所以未排列的数字个数是n-i
那么上源代码:
int temp = 0;//这个用于后续进行换位时,临时存储其中一个元素
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
//如果前一个数比后一个数大,就交换他们的位置
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
使用for循环嵌套完成核心代码的书写(也可以使用别的)
如果实在看不懂,可以找一找动画过程,或者看看视频,b站上黑马程序员的c++视频有解释,讲的很细。
Vector——c++动态数组标准库
额,为什么会用到这个呢?
好问题,我在开始写的时候,就想着我能直接在控制台直接输入数字,所以就有了它!!!
Vector是 C++ 标准库中的一个类模板,用于实现动态数组,提供了许多便捷的函数和方法来处理数组。
下面是
std::vector
常用的一些函数和方法:
构造和初始化:
std::vector<T> v
:创建一个空的std::vector
,其中T
是存储在vector
中的元素类型。std::vector<T> v(n)
:创建一个包含n
个默认构造的元素的std::vector
。std::vector<T> v(n, value)
:创建一个包含n
个值为value
的元素的std::vector
。大小和容量:
v.size()
:返回v
中元素的数量。v.empty()
:检查v
是否为空。v.capacity()
:返回v
可以容纳的元素数量。v.reserve(n)
:请求将v
的容量增加到至少n
。访问元素:
v[index]
:返回位于索引index
处的元素的引用。v.at(index)
:返回位于索引index
处的元素的引用,并会进行越界检查。v.front()
:返回v
的第一个元素的引用。v.back()
:返回v
的最后一个元素的引用。添加和删除元素:
v.push_back(value)
:在v
的末尾添加一个值为value
的元素。v.pop_back()
:从v
的末尾移除最后一个元素。v.insert(iterator, value)
:在iterator
所指向的位置之前插入一个值为value
的元素。v.erase(iterator)
:从v
中移除由iterator
指定的元素。其他常用操作:
v.clear()
:移除v
中的所有元素。v.swap(other)
:交换v
与other
中的元素。
这只是一些最基础的常用函数,差不多够用。
源代码及其详解
#include<iostream>
#include<vector>//引用c++动态数组标准库
using namespace std;
/*
冒泡排序算法
*/
template<typename T>
void bubble_sort(vector<T>& arr)
{
int n = arr.size(); // 数组大小
bool swapped; // 标记是否发生过交换
for (int i = 0; i < n - 1; i++)
{
swapped = false; // 每一轮开始时,标记为未发生交换
for (int j = 0; j < n - i - 1; j++)
{
if (arr[j] > arr[j + 1]) // 如果前一个元素大于后一个元素
{
swap(arr[j], arr[j + 1]); // 交换两个元素的位置
swapped = true; // 有发生交换,标记为已交换
}
}
if (!swapped) break; // 如果本轮没有发生交换,提前结束排序
}
}
int main()
{
vector<int> arr; // 存储输入数字的向量
int num = 0;
cout << "请输入一串数字(以空格或换行分隔,输入非数字结束):" << endl;
while (cin >> num)
{
arr.push_back(num); // 将输入的数字添加到向量中
}
cout << "输入的数字为:";
for (auto x : arr)
{
cout << x << " "; // 输出输入的数字
}
cout << endl;
bubble_sort(arr); // 对输入的数字进行冒泡排序
cout << "排序后的数字为:";
for (auto x : arr)
{
cout << x << " "; // 输出排序后的数字
}
cout << endl;
return 0;
}
template<typename T>
函数模板的声明语法,其中 typename T
是函数模板的类型参数,表示在函数定义中可以使用一个名为 T
的类型。这是一种泛型编程技术,也称为模板编程。
通过使用函数模板,我们可以编写一次代码,然后用不同类型的参数调用该函数,从而对多种类型的数据都进行相同的操作。在冒泡排序的例子中,我们使用了模板类型 T
,以便让函数可以处理任意类型的数组(整数、浮点数、字符等)。
在函数模板的定义中,我们使用类型参数 T
代替实际的类型,但是在函数模板调用时,需要将实际类型作为参数传递给函数。例如,在冒泡排序的例子中,我们可以这样调用函数:
std::vector<int> arr = {3, 5, 2, 8, 4, 7};
bubble_sort<int>(arr);
这里的 <int>
指定了类型参数 T
的实际类型为 int
,从而在函数调用中对 arr
数组进行排序。
使用函数模板可以增加代码的重用性和灵活性,减少代码的冗余,提高程序的可读性和可维护性。
void bubble_sort(vector<T>& arr)
&
是引用符号。它表示函数的参数arr
是一个对类型为std::vector<T>
的对象的引用。通过将参数声明为引用,可以避免在函数调用时进行对象的复制,从而提高程序的效率。引用允许我们在函数内部直接操作传递给函数的原始对象,而不是副本。
在冒泡排序的例子中,
std::vector<T>& arr
表示arr
是一个引用,指向类型为std::vector<T>
的对象。当我们在函数内部修改arr
时,实际上就是在修改传递给函数的原始数组。使用引用作为函数参数还可以使得函数能够修改传入的对象,而不仅仅是对副本进行操作。这样,函数执行后,原始对象的内容也会发生变化。
总而言之,
&
是引用符号,用于将参数声明为对原始对象的引用,从而避免对象的复制,并使函数能够修改传入的对象。
这个举个例子说就是:
int& element = v[2];
这个语句将 v[2]
的引用赋值给了变量 element
,而不是将其值赋值给 element
。
在这里使用 int&
是为了声明 element
为一个整数的引用,也就是说 element
将成为指向 v[2]
的引用。通过这样的声明,对 element
的修改将直接影响到 v[2]
。反之亦然,对 v[2]
的修改也会影响到 element
。
以下是一个示例:
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v = {1, 2, 3, 4, 5};
int& element = v[2];
element = 10; // 修改 element,也同时修改了 v[2]
std::cout << v[2] << std::endl; // 输出修改后的值,即 10
std::cout << element << std::endl; // 输出修改后的值,即 10
return 0;
}
输出结果将会是:
10
10
因此,引用的声明可以让我们通过改变引用来直接修改底层变量的值。在这种情况下,element
成为了 v[2]
的别名。
最后插一嘴,vs还是安装在c盘比较好,我换成其他盘之后,每次调式都要卡好一会,之前一下子就好了。