算法分析与设计 练习一
内容一
分别针对随机生成的若干组整数序列进行排序,排序算法使用四种方法。
1.InsertSort
源码:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace std;
//插入排序
void InsertSort(vector<int>& nums)
{
int size = nums.size();
//依次将nums[1]-nums[n-1]插入前面已排序序列,初始默认nums[0]有序
for (int i = 1;i < size;i++)
{
//如果nums[i]小于前驱,需将nums[i]插入前面已排序序列
if (nums[i] < nums[i - 1])
{
int temp = nums[i];
int j;
for (j = i - 1;j >= 0 && temp < nums[j];j--)
{
nums[j + 1] = nums[j];
}
nums[j + 1] = temp;
}
}
}
int main()
{
//创建一个包含1000个范围在1-1000随机整数的数组
vector<int> nums1(1000);
srand(time(0));
for (int i = 0;i < 1000;i++)
{
nums1[i] = 1 + rand() % 1000;
}
//插入排序
clock_t start1 = clock();
InsertSort(nums1);
clock_t finish1 = clock();
double duration1 = static_cast<double>(finish1 - start1);
cout << "1000个随机数的运行时间为: " << duration1 << "毫秒" << endl;
//创建一个包含10000个范围在1-10000随机整数的数组
vector<int> nums2(10000);
srand(time(0));
for (int i = 0;i < 10000;i++)
{
nums2[i] = 1 + rand() % 10000;
}
//插入排序
clock_t start2 = clock();
InsertSort(nums2);
clock_t finish2 = clock();
double duration2 = static_cast<double>(finish2 - start2);
cout << "10000个随机数的运行时间为: " << duration2 << "毫秒" << endl;
//创建一个包含100000个范围在1-100000随机整数的数组
vector<int> nums3(100000);
srand(time(0));
for (int i = 0;i < 100000;i++)
{
nums3[i] = 1 + rand() % 100000;
}
//插入排序
clock_t start3 = clock();
InsertSort(nums3);
clock_t finish3 = clock();
double duration3 = static_cast<double>(finish3 - start3);
cout << "100000个随机数的运行时间为: " << duration3 << "毫秒" << endl;
return 0;
}
时间复杂度:
最好情况下的时间复杂度为:O(N)
平均情况下的时间复杂度为:O(N^2)
最坏情况下的时间复杂度为:O(N^2)
空间复杂度: O(1)
稳定性:稳定
2.BubbleSort
源码:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace std;
//双向冒泡排序
void BubbleSort(vector<int>& nums)
{
int low = 0, high = nums.size()-1;
//一趟冒泡后记录元素是否交换标志,如果一趟冒泡后为false则说明未发生交换,即已有序
bool flag = true;
while(low <= high && flag)
{
flag = false;
//从前向后冒泡,每趟把最大的元素放在最后面
for (int i = low;i < high;i++)
{
if (nums[i] > nums[i + 1])
{
int temp1 = nums[i];
nums[i] = nums[i + 1];
nums[i + 1] = temp1;
flag = true;
}
}
high--;
//从后往前冒泡,每趟把最小的元素放在最前面
for(int i = high;i > low;i--)
{
if(nums[i] < nums[i-1])
{
int temp2 = nums[i];
nums[i] = nums[i - 1];
nums[i - 1] = temp2;
flag = true;
}
}
low++;
}
}
int main()
{
//创建一个包含1000个范围在1-1000随机整数的数组
vector<int> nums1(1000);
srand(time(0));
for (int i = 0;i < 1000;i++)
{
nums1[i] = 1 + rand() % 1000;
}
//冒泡排序
clock_t start1 = clock();
BubbleSort(nums1);
clock_t finish1 = clock();
double duration1 = static_cast<double>(finish1 - start1);
cout << "1000个随机数的运行时间为: " << duration1 << "毫秒" << endl;
//创建一个包含10000个范围在1-10000随机整数的数组
vector<int> nums2(10000);
srand(time(0));
for (int i = 0;i < 10000;i++)
{
nums2[i] = 1 + rand() % 10000;
}
//冒泡排序
clock_t start2 = clock();
BubbleSort(nums2);
clock_t finish2 = clock();
double duration2 = static_cast<double>(finish2 - start2);
cout << "10000个随机数的运行时间为: " << duration2 << "毫秒" << endl;
//创建一个包含100000个范围在1-100000随机整数的数组
vector<int> nums3(100000);
srand(time(0));
for (int i = 0;i < 100000;i++)
{
nums3[i] = 1 + rand() % 100000;
}
//冒泡排序
clock_t start3 = clock();
BubbleSort(nums3);
clock_t finish3 = clock();
double duration3 = static_cast<double>(finish3 - start3);
cout << "100000个随机数的运行时间为: " << duration3 << "毫秒" << endl;
return 0;
}
时间复杂度:
最好情况下的时间复杂度为:O(N)
平均情况下的时间复杂度为:O(N^2)
最坏情况下的时间复杂度为:O(N^2)
空间复杂度: O(1)
稳定性:稳定
3.QuickSort
源码:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace std;
//划分算法,即一趟排序过程
int Partition(vector<int>& nums, int low, int high)
{
//将数组中的第一个元素作为枢纽值,对数组进行划分
int pivot = nums[low];
while (low < high)
{
//比枢纽值小的元素移动到枢纽值的左端
while (low < high && nums[high] >= pivot) high--;
nums[low] = nums[high];
//比枢纽值大的元素移动到枢纽值的右端
while (low <high && nums[low] <= pivot) low++;
nums[high] = nums[low];
}
//枢纽值存放在最终位置
nums[low] = pivot;
//返回存放枢纽值的最终位置
return low;
}
void QuickSort(vector<int>& nums, int low, int high)
{
if (low < high)
{
//划分
int pivotpos = Partition(nums, low, high);
//左半部分
QuickSort(nums, low, pivotpos-1);
//右半部分
QuickSort(nums, pivotpos+1, high);
}
}
int main()
{
//创建一个包含1000个范围在1-1000随机整数的数组
vector<int> nums1(1000);
srand(time(0));
for (int i = 0;i < 1000;i++)
{
nums1[i] = 1 + rand() % 1000;
}
//快速排序
clock_t start1 = clock();
QuickSort(nums1, 0, 999);
clock_t finish1 = clock();
double duration1 = static_cast<double>(finish1 - start1);
cout << "1000个随机数的运行时间为: " << duration1 << "毫秒" << endl;
//创建一个包含10000个范围在1-10000随机整数的数组
vector<int> nums2(10000);
srand(time(0));
for (int i = 0;i < 10000;i++)
{
nums2[i] = 1 + rand() % 10000;
}
//快速排序
clock_t start2 = clock();
QuickSort(nums2, 0, 9999);
clock_t finish2 = clock();
double duration2 = static_cast<double>(finish2 - start2);
cout << "10000个随机数的运行时间为: " << duration2 << "毫秒" << endl;
//创建一个包含100000个范围在1-100000随机整数的数组
vector<int> nums3(100000);
srand(time(0));
for (int i = 0;i < 100000;i++)
{
nums3[i] = 1 + rand() % 100000;
}
//快速排序
clock_t start3 = clock();
QuickSort(nums3, 0, 99999);
clock_t finish3 = clock();
double duration3 = static_cast<double>(finish3 - start3);
cout << "100000个随机数的运行时间为: " << duration3 << "毫秒" << endl;
return 0;
}
时间复杂度:
最好情况下的时间复杂度为:O(NlogN)
平均情况下的时间复杂度为:O(NlogN)
最坏情况下的时间复杂度为:O(N^2)
空间复杂度: O(logN)
稳定性:不稳定
4.MergeSort
源码:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace std;
//数组nums的左右两段nums[low...mid]和nums[mid+1...high]各自有序,将他们合并成一个有序表
void Merge(vector<int>& nums, vector<int>& copyNums, int low, int mid, int high)
{
int size = nums.size();
//将nums数组中的元素都复制到copyNums中
for (int i = 0;i < size;i++)
{
copyNums[i] = nums[i];
}
int i = low, j = mid + 1, k=i;
while (i <= mid || j <=high)
{
//比较copyNums数组左右两段中的元素,将较小值放入nums数组中
if (i <= mid && j<=high)
{
if (copyNums[i] <= copyNums[j])
{
nums[k++] = copyNums[i++];
}
else
{
nums[k++] = copyNums[j++];
}
}
//将右半部分剩余的元素放入nums数组中
else if (i > mid)
{
nums[k++] = copyNums[j++];
}
//将左半部分剩余的元素放入nums数组中
else
{
nums[k++] =copyNums[i++];
}
}
}
void MergeSort(vector<int>& nums, vector<int>& copyNums, int low, int high)
{
if (low < high)
{
//划分子序列
int mid = low + (high - low) / 2;
MergeSort(nums, copyNums, low, mid);
MergeSort(nums, copyNums, mid+1, high);
//归并
Merge(nums, copyNums, low, mid ,high);
}
}
int main()
{
//创建一个包含1000个范围在1-1000随机整数的数组
vector<int> nums1(1000);
srand(time(0));
for (int i = 0;i < 1000;i++)
{
nums1[i] = 1 + rand() % 1000;
}
//辅助数组copyNums1
vector<int> copyNums1(1000);
//归并排序
clock_t start1 = clock();
MergeSort(nums1, copyNums1, 0, 999);
clock_t finish1 = clock();
double duration1 = static_cast<double>(finish1 - start1);
cout << "1000个随机数的运行时间为: " << duration1 << "毫秒" << endl;
//创建一个包含10000个范围在1-10000随机整数的数组
vector<int> nums2(10000);
srand(time(0));
for (int i = 0;i < 10000;i++)
{
nums2[i] = 1 + rand() % 10000;
}
//辅助数组copyNums2
vector<int> copyNums2(10000);
//归并排序
clock_t start2 = clock();
MergeSort(nums2, copyNums2, 0, 9999);
clock_t finish2 = clock();
double duration2 = static_cast<double>(finish2 - start2);
cout << "10000个随机数的运行时间为: " << duration2 << "毫秒" << endl;
//创建一个包含100000个范围在1-100000随机整数的数组
vector<int> nums3(100000);
srand(time(0));
for (int i = 0;i < 100000;i++)
{
nums3[i] = 1 + rand() % 100000;
}
//辅助数组copyNums3
vector<int> copyNums3(100000);
//归并排序
clock_t start3 = clock();
MergeSort(nums3, copyNums3, 0, 99999);
clock_t finish3 = clock();
double duration3 = static_cast<double>(finish3 - start3);
cout << "100000个随机数的运行时间为: " << duration3 << "毫秒" << endl;
return 0;
}
时间复杂度:
最好情况下的时间复杂度为:O(NlogN)
平均情况下的时间复杂度为:O(NlogN)
最坏情况下的时间复杂度为:O(NlogN)
空间复杂度: O(N)
稳定性:稳定
5.快速排序一趟Partition过程
初始序列:46, 38, 19, 45, 55, 92, 24, 46, 90
选取第一个元素作为pivot, 即pivot = 46
Partition: 24, 38, 19, 45, 55, 92, 46, 46, 90
24, 38, 19, 45, 46, 92, 55, 46, 90
结果: 24, 38, 19, 45, 46, 92, 55, 46, 90
内容二 众数问题
在一个包含n个元素的多重集合S中,每个元素在S中出现的次数称为该元素的重数,多重集合S中重数最大的元素称为众数。
现要求对随机生成的由n个自然数组成的多重集合S, 编程计算S的众数及其重数
1.Mode
源码:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <unordered_map>
using namespace std;
//寻找众数及其重数
pair<int, int> findMode(vector<int>& nums)
{
//m的键为数组元素,m的值为一个数据元素在数组中出现的频率
unordered_map<int, int> m;
for (int i = 0;i < nums.size();i++)
{
//统计元素频率
m[nums[i]]++;
}
//将统计结果存放在数组vec中
vector<pair<int, int>> vec(m.begin(), m.end());
//将众数和重数存放在mode中
pair<int, int> mode;
//找出众数和重数
int maxValue = vec[0].second;
for (int i = 1;i < vec.size();i++)
{
if (vec[i].second > maxValue)
{
maxValue = vec[i].second;
mode = vec[i];
}
}
return mode;
}
int main()
{
//创建一个包含10个范围在1-10随机整数的数组
vector<int> nums(10);
srand(time(0));
for (int i = 0;i < 10;i++)
{
nums[i] = 1 + rand() % 10;
}
//打印数组
for(int i = 0;i < 10;i++)
{
cout << nums[i] << " ";
}
cout << endl;
cout << "众数: " << findMode(nums).first << " 重数: " << findMode(nums).second << endl;
return 0;
}
思路:
算法使用一个unordered_map的数据结构, 其键(key)为数组元素, 其值(value)为一个数据元素在数组中出现的频率。 算法遍历一次数组, 统计各数据元素出现的频率, 然后将统计结果存放在数组vec中, 最后遍历数组vec, 找出众数和重数放在mode中并返回。
时间复杂度:O(N)
空间复杂度:O(N)
内容三 最近点对问题
最近点对问题描述:对平面上给定的N个点,给出所有点对的最短距离。即,输入是平面上的N个点,输出是N点中具有最短距离的两点。
要求随机生成N个点的平面坐标,应用穷举法编程计算出所有点对的最短距离
要求随机生成N个点的平面坐标,应用分治法编程计算出所有点对的最短距离
1.ClosestPoints
源码:
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <ctime>
using namespace std;
//Point类
class Point
{
public:
int x;
int y;
};
//计算两点之间的距离
float distance(const Point& p1, const Point& p2)
{
return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
//穷举法
/*float closestPoints(Point points[], Point record[], int n)
{
float minDist = 9999;
for (int i = 0;i < n;i++)
{
for (int j = i +1;j < n;j++)
{
float dist = distance(points[i], points[j]);
if (dist < minDist)
{
minDist = dist;
record[0] = points[i];
record[1] = points[j];
}
}
}
return minDist;
}*/
//按x值升序排序
bool cmpX(const Point& p1, const Point& p2)
{
return p1.x < p2.x;
}
//按y值升序排序
bool cmpY(const Point& p1, const Point& p2)
{
return p1.y < p2.y;
}
//分治法,
float closestPointsHelper(Point points[], Point record[], int left, int right)
{
//f1记录左半边点的最近距离,f2记录右半边点的最近距离,f3记录横跨两边的点的最近距离
float f1, f2, f3, f;
//只有两个点的情况
if (right - left == 1)
{
f = distance(points[left], points[right]);
record[0] = points[left];
record[1] = points[right];
return f;
}
//只有三个点的情况
if (right - left == 2)
{
f1 = distance(points[left], points[left + 1]);
f2 = distance(points[left + 1], points[right]);
f3 = distance(points[left], points[right]);
if((f1 < f2) && (f1 < f3))
{
f = f1;
record[0] = points[left];
record[1] = points[left + 1];
}
else if (f2 < f3)
{
f = f2;
record[0] = points[left + 1];
record[1] = points[right];
}
else
{
f = f3;
record[0] = points[left];
record[1] = points[right];
}
return f;
}
//大于3个点的情况
int mid = left + (right - left) / 2;
//划分左半边
f1 = closestPointsHelper(points, record, left, mid);
//辅助数组temp1
Point temp1[2];
temp1[0] = record[0];
temp1[1] = record[1];
划分右半边
f2 = closestPointsHelper(points, record, mid + 1, right);
//辅助数组temp1
Point temp2[2];
temp2[0] = record[0];
temp2[1] = record[1];
//取左、右两边点的距离的较小值
if (f1 < f2)
{
f = f1;
record[0] = temp1[0];
record[1] = temp1[1];
}
else
{
f = f2;
record[0] = temp2[0];
record[1] = temp2[1];
}
//辅助数组midPoints
int index = 0;
Point midPoints[right - left + 1];
//以mid为中心,f为半径划分一个长带,最小点对还有可能存在于这个长带中
for (int i = mid;(i >= left) && (points[mid].x - points[i].x < f);i--)
{
midPoints[index++] = points[i];
}
for (int i=mid + 1;(i <= right) && (points[i].x - points[mid].x <f);i++){
midPoints[index++] = points[i];
}
//按y值升序排序
sort(midPoints, midPoints + index, cmpY);
//找出长带中可能存在的最小距离
for (int i = 0;i < index;i++)
{
for (int j = i + 1;j < index;j++)
{
//如果两点y值相减大于f,则这两点不可能是最近点对
if ((midPoints[j].y - midPoints[i].y) >= f)
{
break;
}
else{
f3 = distance(midPoints[i], midPoints[j]);
if (f3 < f)
{
f = f3;
record[0] = midPoints[i];
record[1] = midPoints[j];
}
}
}
}
return f;
}
float closestPoints(Point points[], Point record[])
{
return closestPointsHelper(points, record, 0, 9);
}
int main()
{
//随机生成10个点
Point points[10];
srand(time(0) + 1);
for (int i = 0;i < 10;i++)
{
points[i].x = 1 + rand() % 20;
}
srand(time(0) + 2);
for (int i = 0;i < 10;i++)
{
points[i].y = 1 + rand() % 20;
}
//打印点集
cout << "点集: " << endl;
for (int i = 0;i < 10;i++)
{
cout << "(" << points[i].x << "," << points[i].y << ")" << endl;
}
//按y值升序排序
sort(points, points + 10, cmpX);
//数组record记录最近点对
Point record[2];
//float minDistance = closestPoints(points, record, 10);
float minDistance = closestPoints(points, record);
cout << "最近点对是: " << "(" << record[0].x << "," << record[0].y << ")";
cout << endl;
cout << " " << "(" << record[1].x << "," << record[1].y << ")";
cout << endl;
cout << "最近距离为: " << minDistance <<endl;
return 0;
}
思路:
距离的计算公式为:
穷举法通过遍历所有点集,计算出每一个点对的距离,计算出最近的距离并输出。 其主要循环的步骤就是计算距离。
分治法首先考虑将最近对问题进行分治,设计其分治策略。将点集S分成两个子集S1和S2,根据 平衡子问题原则,每个子集中的点数大致都为n/2。这样分治后,最近点对将会出现三种情况:在S1中,在S2中或者最近 点对分别在集合S1和S2中。利用递归分析法分别计算前两种情况,第三种方法另外分析。求解出三类子情况后, 再合并三类情况,比较分析后输出三者中最小的距离。
时间复杂度:
穷举法的时间复杂度:O(N^2)
分治法的时间复杂度:O(NlogN)
空间复杂度:
穷举法的空间复杂度:O(1)
分治法的空间复杂度:O(N)
内容四 最大子序列和
随机给出一个整数序列,选出其中连续且非空的一段使得这段和最大。
1.MaxSubArray
源码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//动态规划
/*int maxSubArray(vector<int>& nums)
{
//数据长度
int size = nums.size();
//pre记录上一个最大子序列和f(i - 1)
int pre = 0;
//result记录最大子序列和
int result = nums[0];
for (int i = 0;i < size;i++)
{
//f(i) = max(f(i - 1) + nums[i], nums[i])
pre = max(pre + nums[i], nums[i]);
result = max(pre, result);
}
return result;
}*/
//分治法
//寻找横跨左、右两边的最大子序列和
int findMaxCrossingSubArray(vector<int>& nums, int left, int mid, int right)
{
int leftSum = INT_MIN;
int sum = 0;
for (int i = mid;i >= left;i--)
{
sum = sum + nums[i];
leftSum = max(sum, leftSum);
}
int rightSum = INT_MIN;
sum = 0;
for (int i = mid + 1;i <= right;i++)
{
sum = sum + nums[i];
rightSum = max(sum, rightSum);
}
int maxCrossingSum = leftSum + rightSum;
return maxCrossingSum;
}
int maxSubArrayHelper(vector<int>& nums, int left, int right)
{
//数组中只有一个数据元素的情况
if (left == right)
{
return nums[left];
}
//其他情况
int mid = left + (right - left) / 2;
//左半边的最大子序列和
int leftSum = maxSubArrayHelper(nums, left, mid);
//左半边的最大子序列和
int rightSum = maxSubArrayHelper(nums, mid + 1, right);
//横跨左、右两边的最大子序列和
int maxCrossingSum = findMaxCrossingSubArray(nums, left, mid, right);
//比较三者找到最大子序列和
int result = max(leftSum, rightSum);
result = max(result, maxCrossingSum);
return result;
}
int maxSubArray(vector<int>& nums)
{
//数组长度
int size = nums.size();
return maxSubArrayHelper(nums, 0, size - 1);
}
int main()
{
vector<int> nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
for (int i = 0;i < nums.size();i++)
{
cout << nums[i] << " ";
}
cout << endl;
cout << "数组的最大子序列和为: " << maxSubArray(nums) <<endl;
return 0;
}
思路:
动态规划:用 f(i)代表以第i个数结尾的最大子序列和。
动态规划转移方程为 f(i) = max(f(i - 1) + nums[i], nums[i])。
分治法:取数组中心点作为中心,最大子序列和可能在中心左边,也可能在中心右边,也可能横跨中心。利用递归分析法分别计算前两种情况,第三种方法另外分析。求解出三类子情况后, 再合并三类情况,比较分析后输出三者中最大值。
时间复杂度:
动态规划的时间复杂度:O(N)
分治法的时间复杂度:O(NlogN)
空间复杂度:
动态规划的空间复杂度:O(1)
分治法的空间复杂度:O(logN)
内容五
输入正整数𝑥, 𝑝(𝑥 ≤ 10^9, 𝑝 ≤ 10^18) ,要求输出x^p (10^9+7) 。
1.Mod
源码:
#include <iostream>
using namespace std;
unsigned long long pow(int x, int p)
{
const unsigned long long M = 1000000007;
unsigned long long result = 1;
for (int i = 1;i <= p;i++)
{
//result永远不会超过1000000007
result = (result * x) % M;
}
return result;
}
int main()
{
cout << pow(3, 5) << endl;
cout << pow(3, 10000) << endl;
return 0;
}
内容六
给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
1.TwoSum
源码:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <cstdlib>
#include <ctime>
using namespace std;
vector<int> twoSum(vector<int>& nums, int S)
{
//数据长度
int size = nums.size();
//m的键为数组元素,m的值为该数据元素的下标
unordered_map<int, int> m;
for (int i = 0;i < size;i++)
{
//首先查询哈希表中是否存在target,然后将nums[i]插入到哈希表中,
int target = S - nums[i];
if (m.count(target))
{
//数组中存在两个元素和为S
return vector<int>{i, m[target]};
}
m[nums[i]] = i;
}
//数组中存在两个元素和为S
return vector<int>{-1, -1};
}
int main()
{
//创建一个包含5个范围在1-9随机整数的数组
vector<int> nums(5);
srand(time(0));
for (int i = 0;i < 5;i++)
{
nums[i] = 1 + rand() % 9;
}
//打印数组
for(int i = 0;i < 5;i++)
{
cout << nums[i] << " ";
}
cout << endl;
vector<int> result = twoSum(nums, 7);
cout << result[0] << " " << result[1] <<endl;
return 0;
}
思路:
算法枚举数组中的每一个数 nums[i],寻找数组中是否存在S - nums[i]。算法创建一个哈希表,对于每一个 nums[i],我们首先查询哈希表中是否存在 S - nums[i],然后将nums[i]插入到哈希表中(可保证不会让nums[i]和自己匹配), 这一过程时间复杂度为O(1)。
时间复杂度:O(N)
空间复杂度:O(N)