算法
数据结构中的算法,指的是数据结构所具备的功能
解决特定问题的方法,它是前辈的一些优秀的经验总结
算法的基本特征:(了解)
有穷性(Finiteness)
算法的有穷性是指算法必须能在执行有限个步骤之后终止;
确切性(Definiteness)
算法的每一步骤必须有确切的定义;
输入项(Input)
一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件;
输出项(Output)
一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的;
可行性(Effectiveness)
算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步骤,即每个计算步骤都可以在有限时间内完成(也称之为有效性)。
如何评价一个算法:
时间复杂度:
由于计算机的性能不同,无法准确地统计出一个算法执行所需要的时间。
因此我们用算法执行的次数来代表算法的时间复杂度 O(公式)
常见的时间复杂度:
//O(1)
printf("\n");
//O(n)
for(int i=0;i<n;i++)
//O(log n)
for(int i=n;i>=0;i/=2)
//O(nlogn)
for(int i=0;i<n;i++)
{
for(int j=n;j>=0;j/=2)
{
printf("%d");
}
}
//O(n^2)
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
printf("%d");
}
}
分治:
把一个大而复杂的问题,分解为很多小而简单的问题,利用计算机强大的计算能力来解决问题(循环、递归)
查找算法:
顺序查找:
对待查找的数据没有要求,从头到尾逐一查找,在小规模的数据查找常见,相对效 率低,时间复杂度:O(n)
二分查找:(折半查找)
必须有序,从数据中间位置开始查找,如果中间的值比key小,则继续在右边进行二分查找
时间复杂度:O(logn)
块查找:
是一种对数据进行处理的思想,不是一种特定的算法,当数据量很多时,可以对数据进行分块处理,然后再进行查找,例如英语词典。
权重查找
#include <stdio.h>
#include <stdlib.h>
#define swap(a,b) {typeof(a) t=a;a=b;b=t;}
#define LEN 10
// 顺序查找
int order_search(int* arr,int len,int key)
{
for(int i=0; i<len; i++)
{
if(arr[i] == key) return i;
}
return -1;
}
// 排序
void sort(int* arr,int len)
{
for(int i=0;i<len-1;i++)
{
for(int j=i+1;j<len;j++)
{
if(arr[i] > arr[j]) swap(arr[i],arr[j]);
}
}
}
// 循环二分查找
int binary_search_for(int* arr,int len,int key)
{
int l = 0, r = len - 1;
while(l<=r)
{
int p = (l+r)/2;
if(arr[p] == key) return p;
if(key < arr[p]) r = p-1;
else l = p+1;
}
return -1;
}
int _binary_search(int* arr,int l,int r,int key)
{
if(l > r) return -1;
int p = (l+r)/2;
if(arr[p] == key) return p;
if(key < arr[p])
_binary_search(arr,l,p-1,key);
else
_binary_search(arr,p+1,r,key);
}
// 递归二分查找
int binary_search(int* arr,int len,int key)
{
return _binary_search(arr,0,len-1,key);
}
int main(int argc,const char* argv[])
{
int arr[LEN] = {};
for(int i=0; i<LEN; i++)
{
arr[i] = rand()%100;
printf("%d ",arr[i]);
}
printf("\norder:%d\n",order_search(arr,LEN,93));
sort(arr,LEN);
printf("binary:%d\n",binary_search(arr,LEN,93));
}
排序算法
排序算法的稳定性:
在待排序的数据中,如果有值相同的数据,在排序的过程中如果不会改变它们的相 对顺序,则认为该排序算法为稳定的。
冒泡算法:
对数据左右进行比较,把最大的交换到最后,特定是该算法对数据的有序性敏感,
在排序的过程中发现有序可以立即停止,如果待排序的数据基本有序,则冒泡排序的效率是非常高的
时间复杂度:平均:O(n^2) 最优:O(n)
稳定性: 稳定的
// 冒泡排序
void bubble_sort(int* arr,size_t len)
{
// 标志位判断是否排序完成
bool flag = true;
for(int i=len-1; i>0 && flag; i--)
{
flag = false;
for(int j=0; j<i; j++)
{
//printf("===========\n");
if(arr[j] > arr[j+1])
{
swap(arr[j],arr[j+1]);
flag = true;
}
}
}
show_arr(arr,len);
printf(":%s\n",__func__);
}
选择:
假定最开始的位置是最小值的小标并记录该下标为min,然后与后面的数据进行比较,如果有比min位置小的,则更新min为更小的数据的下标,最后如果最后min的值发生了变化,则交换min位置的数据与最开始位置的数据
虽然时间复杂度较高,但是数据交换次数较少,因此实际运行的速度并不慢、
选择排序是冒泡排序的一个变种,但是对数据的有序性不敏感
时间复杂度:O(n^2)
稳定性: 不稳定的
数据基本有序时,冒泡快;数据较为混乱时,选择快。
// 选择排序
void select_sort(int* arr,size_t len)
{
for(int i=0; i<len-1; i++)
{
int min = i;
for(int j=i+1; j<len; j++)
{
if(arr[j] < arr[min]) min = j;
}
if(min != i) swap(arr[min],arr[i]);
}
show_arr(arr,len);
printf(":%s\n",__func__);
}
插入:
把数据看成两个部分,一部分有序,剩余部分逐个插入进去,当数据全部插入完成后,整个数据就是有序的。
适合对已排序好的数据,新增数据并排序
时间复杂度:O(n^2)
稳定性:稳定的
// 插入排序
void insert_sort(int* arr,size_t len)
{
for(int i=1,j=0; i<len; i++)
{
int val = arr[i];
for(j=i; j>0 && arr[j-1] > val;j--)
{
arr[j] = arr[j-1];
}
if(j != i) arr[j] = val;
}
show_arr(arr,len);
printf(":%s\n",__func__);
}
快速:
找到一个标杆,一方面从左边找比标杆值大的数据,找到后放在标杆的右边,另一方面 从右边找比标杆值小的数据,找到后放在标杆的左边,最终标杆左边的数据都比它小,右边的数据都比它大,这样整体有序,然后再按照相同的方式排序标杆左右两边的数据。
它的综合性能高,因此叫快速排序,笔试考的最多的是快排
时间复杂度:O(nlogn)
稳定性:不稳定
83 86 77 15 [93] 35 86 92 49 21
83 86 77 15 [21] 35 86 92 49 93
15 21 77 86 83 [35] 86 92 49 93
15 21 77 86 83 [77] 86 92 49 93
15 21 35 49 77 86 [86] 92 83 93
15 21 35 49 77 86 [86] 83 92 93
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lpt8n3Eq-1624330094218)(C:\Users\12192\AppData\Roaming\Typora\typora-user-images\image-20210406172624332.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gWhIw51n-1624330094223)(C:\Users\12192\AppData\Roaming\Typora\typora-user-images\image-20210406172650694.png)]
83 86 77 15 [93] 35 86 92 49 21
83 86 77 15 21 35 86 92 49 93
83 86 77 15 [21] 35 86 92 49
15 21 77 86 83 35 86 92 49 93
77 86 83 [35] 86 92 49
15 21 35 86 83 77 86 92 49 93
86 83 [77] 86 92 49
15 21 35 49 77 86 86 92 83 93
86 [86] 92 83
15 21 35 49 77 86 83 86 92 93
[86] 83
15 21 35 49 77 83 86 86 92 93
void _quick_sort(int* arr,int left,int right)
{
int pi = left; // 计算标杆下标位置
int pv = arr[left]; // 备份标杆的值
int l = left, r = right; // 备份左右标杆的下标位置
//当左右标杆相遇结束
while(l < r)
{
// 在标杆右边找比pv小的数据
while(r>pi && arr[r]>=pv) r--;
if(r>pi)
{
//找到了
arr[pi] = arr[r];
pi = r;
}
// 在标杆左边找比pv大的数据
while(l<pi && arr[l]<=pv) l++;
if(l<pi)
{
//找到后
arr[pi] = arr[l];
pi = l;
}
}
// 还原标杆
arr[pi] = pv;
//show_arr(arr,LEN);
//如果左边数据不少2个,左边继续快排
if(pi-left>1) _quick_sort(arr,left,pi-1);
if(right-pi>1)_quick_sort(arr,pi+1,right);
}
// 快速
void quick_sort(int* arr,size_t len)
{
_quick_sort(arr,0,len-1);
show_arr(arr,len);
printf(":%s\n",__func__);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V3aZDS49-1624330094225)(C:\Users\12192\AppData\Roaming\Typora\typora-user-images\image-20210406172958204.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-obl4nmbJ-1624330094229)(C:\Users\12192\AppData\Roaming\Typora\typora-user-images\image-20210406180445281.png)]
归并:
先把一组数据拆分成单独的个体,然后以从小到大的顺序进行合并,由于需要额外使用的内存空间,因此避免了数据的交换的耗时,也是一种典型的以空间换时间的算法
时间复杂度:O(nlogn)
稳定性:稳定的
// 合并
void merge(int* arr,int* tmp,int l,int p,int r)
{
//l是左部分最左 p是左部分最右 p+1是右部分最左 r是右部分最右
//认为合并前 左右部分各自是有序的
if(arr[p] <= arr[p+1]) return;
int i = l, j = p+1, k = l;
while(i<=p && j<=r)
{
// 从左右部分的最左开始,比较谁小谁先放如tmp
if(arr[i] < arr[j])
tmp[k++] = arr[i++];
else
tmp[k++] = arr[j++];
}
// 任意一部分放入结束后,把另一部分剩余的放入tmp末尾
while(i<=p) tmp[k++] = arr[i++];
while(j<=r) tmp[k++] = arr[j++];
// 把tmp合并排序好后的数据重新赋值给arr对应的位置
while(l<=r) arr[l] = tmp[l++];
}
// 拆分
void _merge_sort(int* arr,int* tmp,int l,int r)
{
if(l >= r) return;
int p = (l+r)/2;
_merge_sort(arr,tmp,l,p);
_merge_sort(arr,tmp,p+1,r);
//合并
merge(arr,tmp,l,p,r);
}
// 归并
void merge_sort(int* arr,size_t len)
{
int* tmp = malloc(sizeof(int)*len);
_merge_sort(arr,tmp,0,len-1);
free(tmp);
show_arr(arr,len);
printf(":%s\n",__func__);
}
堆:
把数据当作完全二叉树,然后在树中调整为大根树,然后把根节点交换到末尾,然后数量–,接下去继续调整为大根树,直到节点数量为1时结束,结束后该数据就是有序的。
时间复杂度:O(nlogn)
稳定性:不稳定的
排序 最优 最差 平均 稳定性 特点
堆排的最好、最坏、平均时间复杂度均为O(nlog2n);
快排最坏时间复杂度为**O(n^2),**发生在快排Partition函数不平衡划分的情况下;
简单插入排序最坏时间复杂度为**O(n^2),**发生条件:逆序;
冒泡排序最坏时间复杂度为**O(n^2),**发生条件:逆序;
// 构建堆结构
void create_heap(int* arr,int root,size_t len)
{
// root是堆顶的下标,不是编号
if(root >= len) return;
// left right是左右孩子下标
int left = root*2+1, right = root*2+2;
create_heap(arr,left,len);
create_heap(arr,right,len);
if(right<len && arr[right] >arr[left])
swap(arr[right],arr[left]);
if(left<len && arr[left]>arr[root])
swap(arr[left],arr[root]);
}
// 堆排序
void heap_sort(int* arr,size_t len)
{
//调整为大根树
create_heap(arr,0,len);
//交换堆顶与末尾,然后数量--,并剩余重新调整为大根树
for(int i=len-1; i>=0; i--)
{
swap(arr[0],arr[i]);
create_heap(arr,0,i);
}
show_arr(arr,len);
printf(":%s\n",__func__);
}