算法:
数据结构中的算法,指的是数据结构所具备的功能
解决特定问题的方法,它是前辈们的一些优秀的经验总结
输入:一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算
法本身定出了初始条件;
输出:一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算
法是毫无意义的;
确定性:算法中的每一条指令必须有确切的含义,不能产生多义性:
可行性:算法中的每一条指令必须是切实可执行的,即原则上可以通过已经实现的基
本运算执行有限次来实现(也称之为有效性);
有穷性:算法必须能在有限步后终止;
时间复杂度:
由于计算机的性能不同,无法准确地计算出算法执行所需要的时间
因此我们用算法执行的次数来代表算法的时间复杂度 O(公式) 一般忽略常量
常见的时间复杂度:
// O(1)
printf("%d");
// O(logn)
for(int i=n; i>=0; i=i/2)
{
printf("%d");
}
// O(n)
for(int i=0; i<n; i++)
{
printf("%d");
}
// 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) 平均O(n^2)
稳定的
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++)
{
if(arr[j] > arr[j+1])
{
swap(arr[j],arr[j+1]);
flag = true;
}
}
}
}
选择:
假定最开始的位置是最小值并记录下标min,然后与后面的数据进行比较,
如果有比min为下标的数据小的则更新min,最后如果min的位置与开始位置
发生了改变,则交换min位置的数据与开始位置的数据,以此类推。
虽然时间复杂度挺高的,但是数据交换的次数比较少,因此实际运行时间
并不慢(交换比比较耗时)
时间复杂度: O(n^2)
不稳定 1 10 10
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[i],arr[min]);
}
}
插入:
把数据看作两部分,一部分是有序,把剩余的数据逐个插入进去。
适合对已经排序后的数据,新增数据后进行排序
时间复杂度: O(n^2)
稳定的
void insert_sort(int* arr,size_t len)
{
for(int i=1,j; 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;
}
}
希尔:
是插入排序的增强版,由于插入排序时,数据的移动速度比较慢,所以增加了
增量的概念,以此来提高排序速度
时间复杂度:O(n^2)
不稳定
void shell_sort(int* arr,size_t len)
{
for(int k=len/2; k>0; k/=2)
{
for(int i=k,j; i<len; i++)
{
int val = arr[i];
for(j=i; j-k>=0 && arr[j-k]>val; j-=k)
{
arr[j] = arr[j-k];
}
if(j != i) arr[j] = val;
}
}
}
快速:
找到一个标杆,一方面从左边找比标杆大的数据,找到后把数据放在标杆右边,
另一方面从右边找比标杆小的数据,找到后放在标杆的左边,最终标杆左边的数
据比右边的小,这次就两部分有序,然后用同样的方法排序标杆左边和右边的数
据
特点:综合性最高,因此叫快速排序,笔试面试考得最多的排序算法
时间复杂度:O(nlogn)
不稳定
void _quick_sort(int* arr,int left,int right)
{
if(left >= right) return;
int pi = (left+right)/2;
int pv = arr[pi];
int l = left,r = right;
for(int i=left; i<=right; i++)
{
if(i==pi) printf("[%d] ",arr[i]);
else printf("%d ",arr[i]);
}
printf("\n");
while(l < r)
{
while(l < pi && arr[l] <= pv) l++;
if(l < pi)
{
arr[pi] = arr[l];
pi = l;
}
while(r > pi && arr[r] >= pv) r--;
if(r > pi)
{
arr[pi] = arr[r];
pi = r;
}
}
arr[pi] = pv;
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);
}
归并:
把一组数据拆分成单独的个体,然后按照从小到大的顺序复制到临时空间,
复制完成后再从临时空间拷贝到原内存中。
特点:由于需要使用额外内存空间来避免数据交换的耗时,是一种典型的
以空间换时间的排序算法
时间复杂度: O(nlogn)
稳定的
void merge(int* arr,int* tmp,int l,int p,int r)
{
if(arr[p] < arr[p+1]) return;
int i=l,j=p+1,k=l;
while(i<=p && j<=r)
{
if(arr[i] < arr[j])
tmp[k++] = arr[i++];
else
tmp[k++] = arr[j++];
}
while(i<=p) tmp[k++] = arr[i++];
while(j<=r) tmp[k++] = arr[j++];
memcpy(arr+l,tmp+l,4*(r-l+1));
}
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(4*len);
_merge_sort(arr,tmp,0,len-1);
free(tmp);
}
堆:
先把数据当做完全二叉树,然后把树调整成大根树,然后把根节点交换到数组
最后,然后数量--,然后再调整成大根树,以此类推,直到数量为1时结束,
可以递归、也可以循环实现
时间复杂度:O(nlogn)
不稳定的
void create_heap(int* arr,int root,size_t len)
{
if(root >= len) return;
int left = root*2+1;
int 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);
}
}
void heap_sort_for(int* arr,size_t len)
{
for(int i=len-1; i>0; i--)
{
int p = (i+1)/2-1;
if(arr[p] < arr[i])
swap(arr[p],arr[i]);
}
for(int i=len-1; i>0; i--)
{
swap(arr[0],arr[i]);
for(int j=0; j<i; j++)
{
if(j*2+2 < i && arr[j*2+2] > arr[j*2+1])
swap(arr[j*2+2],arr[j*2+1]);
if(j*2+1 < i && arr[j*2+1] > arr[j])
swap(arr[j*2+1],arr[j]);
}
}
}
计数:
找出数据中的最大值、最小值,并创建哈希表,把数据-最小值作为哈希表数组
的下标来访问哈希表并+1,然后遍历哈希表,如果数据大于0时,则把下标+最
小值还原回数据,依次放回原数组中,达到排序的目的。
该排序算法理论上速度是非常快,但是有很大的局限性,适合整型数据,而且
数据的差值不宜过大,否则会非常浪费内存,也是以空间换时间的排序算法
时间复杂度:O(n+k) k是整数的范围
稳定的
void count_sort(int* arr,size_t len)
{
int min = arr[0],max = arr[len-1];
for(int i=0; i<len; i++)
{
if(arr[i] > max) max = arr[i];
if(arr[i] < min) min = arr[i];
}
int* tmp = calloc(4,max-min+1);
for(int i=0; i<len; i++)
{
tmp[arr[i]-min]++;
}
for(int i=0,j=0; i<max-min+1; i++)
{
while(tmp[i]--)
{
arr[j++] = i+min;
}
}
free(tmp);
}
桶:
把数据根据值来存储到不同的桶,然后再调用其它的排序算法,对桶中的数据
进行排序,然后再拷贝回数组中,以此达到降低排序规模来减少排序的时间,
也是以空间换时间的方式。
缺点:如何分桶,桶定义多大
时间复杂度:O(n+k)
稳定的
基数:
是桶排序的具体实现,首先创建10个队列,然后逆序计算出数据的个十百千...
然后分别压入对应的队列中,然后从队列中弹出存储的数据到数组中。
缺点:只适合排序正整数,需要准备很大的空间,也是空间换时间
时间复杂度:O (nlog(r)m),其中r为所采取的基数,而m为堆数
稳定的