排序起点:插入排序和冒泡排序
前言
本节利用C语言和C++两种语言的对插入排序和冒泡排序进行实现并测试。并分析了两种排序的时间复杂度
本节重点
插入排序
冒泡排序
时间复杂度的分析
总结
一、插入排序
排序思想:在一组有序的数据中插入一个数,使插入后的数据仍然有序
我们模拟的是对一个数组进行排序,数组的数据个数记为n,比如要将第i个元素插入排序到前i-1个元素中,使这i个元素有序。从头开始,对每次遍历到的数据下一个数据进行插入排序,直到插入数组中的最后一个数据为止。当插入最后一个数据的时候,这种情况相当于向前n-1个数据(有序)中插入数组中的最后一个数据,使插入后的数据仍然保持有序
操作步骤:
- 遍历待插入的数据序列
- 保存待插入的数据
- 找到插入的位置
- 插入数据
代码实现:
C语言版本
```cpp
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = a[end + 1];//先对要插入的数据进行保存,防止后面被覆盖
while (end >= 0)//找到插入的位置
{
if (tmp < a[end])
{
//因为要排成升序,因此如果插入的数据比已知数据中的数据要小,就应该将该数据向后移动
a[end + 1] = a[end];
end--;
}
else
{
//如果插入的数据比遍历到的数据大,或者相等,即找到插入的位置
break;
}
}
//将数据插入到数组
a[end + 1] = tmp;
}
}
注意:遍历的过程记得要从后往前遍历,因为在后面的比较过程中,如果需要挪动数据,是会将数据往后挪动的,那么如果没有提前将待插入的数据保存,那么被覆盖的话就找不到该数据了
代码中,end相当于记录已知序列是数组中的前end个元素,第end+1就是要插入的数据
C++版本
class myvector
{
public:
void InsertSort()
{
int i = 0;
for (i = 0; i < size() - 1; i++)
{
int end = i;
int tmp = _a[end + 1];
while (end >= 0)
{
if (tmp < _a[end])
{
_a[end + 1] = _a[end];
end--;
}
else
{
break;
}
}
_a[end + 1] = tmp;
}
}
void Print()
{
for (int i = 0; i < size(); i++)
{
cout << _a[i] << " ";
}
cout << endl;
}
void push_back(int x)
{
if (_size == _capacity)
{
//扩容
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
_a = new int[newcapacity];
_capacity = newcapacity;
}
_a[_size] = x;
_size++;
}
size_t size()
{
return _size;
}
private:
int* _a;
size_t _size = 0;
size_t _capacity = 0;
};
测试逻辑
int main()
{
myvector v;//创建新对象
//插入数据
v.push_back(2);
v.push_back(1);
v.push_back(4);
v.push_back(5);
v.Print();
//调用选择排序进行排序
v.InsertSort();
v.Print();
return 0;
}
冒泡排序
步骤:
- 确定需要进行几趟冒泡排序
- 需要两个循环变量
思想:第一轮冒泡排序的结果就是将数据中的最大值放到了最后一个位置,第二轮冒泡排序针对的数据是剩余的n-1个元素,将剩下的n-1个元素中的最大值放到倒数第二个位置,依此类推!
代码实现:
C语言版本
void BubbleSort(int* a, size_t n)
{
int i = 0;
for (i = 0; i < n - 1; i++)
{
int j = 0;
for (j = 1; j < n - i; j++)
{
if (a[j] < a[j - 1])
{
swap(a[j], a[j - 1]);
}
}
}
}
测试逻辑
int main()
{
int a[] = { 9,8,7,6,5,4,3,2,1 };
size_t n = sizeof(a) / sizeof(int);
BubbleSort(a, n);
PrintArr(a, n);
}
运行结果
改进版本
void BubbleSort(int* a, size_t n)
{
int i = 0;
int exchange = 1;
for (i = 0; i < n - 1; i++)
{
int j = 0;
for (j = 1; j < n - i; j++)
{
if (a[j] < a[j - 1])
{
exchange = 0;
swap(a[j], a[j - 1]);
}
}
if (exchange == 1)
{
break;
}
}
}
C++版本
class myvector
{
public:
void BubbleSort()
{
int i = 0;
for (i = 0; i < size() - 1; i++)
{
int j = 1;
int exchange = 1;
for (j = 1; j < size() - i; j++)
{
if (_a[j] < _a[j - 1])
{
swap(_a[j], _a[j - 1]);
exchange = 0;
}
}
if (exchange == 1)
{
break;
}
}
}
void Print()
{
for (int i = 0; i < size(); i++)
{
cout << _a[i] << " ";
}
cout << endl;
}
void push_back(int x)
{
if (_size == _capacity)
{
//扩容
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
_a = new int[newcapacity];
_capacity = newcapacity;
}
_a[_size] = x;
_size++;
}
size_t size()
{
return _size;
}
private:
int* _a;
size_t _size = 0;
size_t _capacity = 0;
};
测试逻辑
int main()
{
myvector v;
v.push_back(2);
v.push_back(1);
v.push_back(4);
v.push_back(5);
v.Print();
v.BubbleSort();
v.Print();
return 0;
}
运行结果
时间复杂度分析
1. 插入排序时间复杂度为O(N^2)
最差的情况:就是当数据刚好是逆序有序的时候,那么在挪动数据步骤,第一个数据需要挪动0次,第二个数据需要挪动1次,第三个数据需要挪动2次,一次类推,第N个数据需要挪动N-1次,因此,所有的挪动次数就应该是:
1+2+3+…+N-1 = (1+N-1)(N-1)/2 = N(N-1)/2 = O(N^2)
最好的情况:当数据有序的时候,不需要挪动数据,但是也是需要遍历数据的,所以时间复杂度为:O(N)
2. 冒泡排序的时间复杂度
第一趟冒泡排序中需要比较的数据的次数为N-1,第二躺冒泡排序需要比较的次数为N-2,…,因此冒泡排序的时间复杂度的算法是典型的等差数列求和:
1+2+3+…+N-1 = (1+N-1)(N-1)/2 = N(N-1)/2 = O(N^2)
总结
- 插入排序在数据为逆序的时候,需要的时间比较长,时间复杂度为O(N^2),性能较差。
- 冒泡排序在任何情况下都是需要O(N^2)的时间复杂度,整体性能也是偏差,本节中另外实现了一种改进方式,如果数据在冒泡排序的过程中出现有序,则不再需要进行冒泡排序,会退出循环,从而增加了排序性能,减少了比较次数