备战秋招,复习基础。如有错误,欢迎批评指正,共同进步!
数据结构
| 数据结构 | 查找 | 插入 | 删除 | 遍历 |
|---|---|---|---|---|
| 数组 | O(n) | O(1) | O(n) | - |
| 有序数组 | O(logn) | O(n) | O(n) | O(n) |
| 链表 | O(n) | O(1) | O(n) | - |
| 有序链表 | O(n) | O(n) | O(n) | O(n) |
| 二叉树(一般情况) | O(logn) | O(logn) | O(logn) | O(n) |
| 二叉树(最坏情况) | O(n) | O(n) | O(n) | O(n) |
| 平衡树(一般+最坏) | O(logn) | O(logn) | O(logn) | O(n) |
| 哈希表 | O(1) | O(1) | O(1) | - |
树
二叉树遍历
前序遍历:根 左 右 中序遍历:左 根 右 后序遍历:左 右 根
赫夫曼树
最优二叉树,带权路径长度(树根到每一个节点的路径长度与节点的权的乘积的和)最小 构造:
1 给定n个带权值的节点,集合为F
2 从F中选取两颗根节点权值最小的树,作为左右子树,构造新的二叉树,新根节点的权值为左右子树根节点权值之和
3 从F中删除两棵子树,同时将新二叉树加入F
4 重复2和3,直到只剩一棵树。
哈希
在纪录的存储位置和它的关键字之间建立一个对应关系f,使每个关键字和结构中一个唯一的存储位置与之对应。这个对应关系称为哈希函数。
解决冲突的办法
1 开放定址法:H(key)+di ← 增量序列,线性再探测/二次探测/随机探测
2 再哈希法:计算另一个哈希函数的地址,但会增加计算时间
3 链地址法:所有关键字为同义词的纪录存储在同一个线性链表中
4 建立公共溢出区
堆
堆是具有以下性质的完全二叉树:
- 每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;
- 或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
栈
C语言实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define maxn 110//栈的最大值
typedef int elem; //方便修改数据类型
typedef struct{
int top; ///栈顶的索引
elem index[maxn];
}Stack;
Stack stack; ///为了方便这里直接定义全局变量了,用局部变量的话在每个函数加上取地址符和声明就行了
void stack_pop(){ ///元素出栈,此函数无返回值
stack.top--;
}
void stack_push(elem buf){ ///元素入栈
stack.index[++stack.top] = buf;
}
int stack_empty(){///判空,如果栈为空的话返回1,否则返回0
return stack.top == -1;
}
void stack_clear(){ ///清空栈
stack.top = -1;
}
int stack_size(){ ///求栈内元素数
return stack.top+1;
}
elem stack_top(){ ///返回栈顶元素
return stack.index[stack.top];
}
int main()
{
stack_clear();///初始化栈
elem buf;
buf = 10;
stack_push(buf);
printf("%d\n",stack_top());
printf("%d\n",stack_empty());
printf("%d\n",stack_size());
stack_pop();
printf("%d\n",stack_size());
return 0;
}
常用算法
排序
| 方法 | 平均时间 | 最坏情况 | 最好情况 | 稳定度 | 额外空间 | 备注 |
|---|---|---|---|---|---|---|
| 直接插入 | O(n^2) | O(n^2) | O(n) | 稳定 | O(1) | 大部分已排序时较好 |
| 希尔 | O(nlogn) | O(nlogn) | 与步长相关 | 不稳定 | O(1) | n小时较好 |
| 冒泡 | O(n^2) | O(n^2) | O(n) | 稳定 | O(1) | n小时较好 |
| 快排 | O(nlogn) | O(n^2) | O(nlog2n) | 不稳定 | O(log2n) | n大时较好,基本有序时反而不好 |
| 直接选择 | O(n^2) | O(n^2) | O(n^2) | 不稳定 | O(1) | n小时较好 |
| 二叉树排序 | O(nlog2n) | O(n^2) | 不一定 | O(1) | ||
| 堆排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | 不稳定 | O(1) | n大时较好 |
| 归并 | O(nlogn) | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大时较好 |
| 基数 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | 稳定 | O(r) | d为位数,r为基数 |
| 计数 | O(n+k) | O(n+k) | O(n+k) | 稳定 | O(n+k) | 优于比较排序,0~k为数值范围 |
| 桶排序 | O(n+c) | O(nlogn)所有元素落到一个桶中 | O(n) | 稳定 | O(n+m) | n为数的个数,m为桶数,c=n*(logn-logm)桶越多效率越高。当n=m时达到O(n)但占用空间大。桶内可用快排 |
快速排序
1 设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2 以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3 从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]的值交换;
4 从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]的值交换;
5 重复第3、4步,直到i=j;
/**
题目:快速排序算法
思路:两个哨兵,i,j,j从右边找比基数小的,i从左边找比基数大的,然后交换两个目标元素的位置,直到i=j,然后交换i和基数的位置,递归处理。
**/
function quick_sort(arr,from,to){
var i = from; //哨兵i
var j = to; //哨兵j
var key = arr[from]; //标准值
if(from >= to){ //如果数组只有一个元素
return;
}
while(i < j){
while(arr[j] > key && i < j){ //从右边向左找第一个比key小的数,找到或者两个哨兵相碰,跳出循环
j--;
}
while(arr[i] <= key && i < j){ //从左边向右找第一个比key大的数,找到或者两个哨兵相碰,跳出循环,这里的=号保证在本轮循环结束前,key的位置不变,否则的话跳出循环,交换i和from的位置的时候,from位置的上元素有可能不是key
i++;
}
/**
代码执行道这里,1、两个哨兵到找到了目标值。2、j哨兵找到了目标值。3、两个哨兵都没找到(key是当前数组最小值)
**/
if(i < j){ //交换两个元素的位置
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
arr[from] = arr[i] //
arr[i] = key;
quick_sort(arr,from,i-1);
quick_sort(arr,i+1,to);
}
var arr = [3,3,-5,6,0,2,-1,-1,3];
console.log(arr);
quick_sort(arr,0,arr.length-1);
console.log(arr);
C语言: 参考:C语言实现----快速排序
#include<stdio.h>
void Swap(int arr[], int low, int high)
{
int temp;
temp = arr[low];
arr[low] = arr[high];
arr[high] = temp;
}
int Partition(int arr[], int low, int high)
{
int base = arr[low];
while(low < high)
{
while(low < high && arr[high] >= base)
{
high --;
}
Swap(arr, low, high);
while(low < high && arr[low] <= base)
{
low ++;
}
Swap(arr, low, high);
}
return low;
}
void QuickSort(int arr[], int low, int high)
{
if(low < high)
{
int base = Partition(arr, low, high);
QuickSort(arr, low, base - 1);
QuickSort(arr, base + 1, high);
}
}
int main()
{
int n;
scanf("%d\n",&n);
int arr[n];
int i , j;
for(i = 0; i < n; i ++)
{
scanf("%d",&arr[i]);
}
printf("\n");
QuickSort(arr, 0, n-1);
for(j = 0; j < n; j ++)
{
printf("%4d",arr[j]);
}
return 0;
}
冒泡排序
1 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3 针对所有的元素重复以上的步骤,除了最后一个。
4 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
function bSort(arr) {
var len = arr.length;
for (var i = 0; i < len-1; i++) {
for (var j = 0; j < len - 1 - i; j++) {
// 相邻元素两两对比,元素交换,大的元素交换到后面
if (arr[j] > arr[j + 1]) {
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
return arr;
}
//举个数组
myArr = [20,18,27,19,35];
//使用函数
bSort(myArr)
C语言: 参考:八种基本的排序(1)——冒泡排序(C语言实现)
//冒泡排序,从小到大(方向可改) 。
#include <stdio.h>
void bubble_sort(int a[], int n); //申明函数"bubble_sort"
int number[10000000]; //在主函数外面定义数组可以更长
void bubble_sort(int a[], int n) //下面是函数bubble_sort的程序
{
int i,j,temp; //定义三个整型变量
for (j=0;j<n-1;j++) //用一个嵌套循环来遍历一遍每一对相邻元素 (所以冒泡函数慢嘛,时间复杂度高)
{
for (i=0;i<n-1-j;i++)
{
if(a[i]>a[i+1]) //从大到小排就把左边的">"改为"<" !!!
{
temp=a[i]; //a[i]与a[i+1](即a[i]后面那个) 交换
a[i]=a[i+1]; //基本的交换原理"c=a;a=b;b=c"
a[i+1]=temp;
}
}
}
}
int main() //主函数
{
int i,n;
printf("输入数字个数:\n");
scanf("%d",&n); //输入数字个数
printf("输入%d个数:\n",n);
for(int j=0;j<n;j++) //用一个for循环来输入所有数字
scanf("%d",&number[j]) ;
bubble_sort(number,n); //引用函数bubble_sort
for (i=0;i<n-1;i++) //输出传来的排序好的数组
printf("%d ",number[i]); //这里这么写是因为有些题有格式要求(最后一个数后面不能有空格)
printf("%d\n",number[i]);
return 0;
}
//ENDING
归并排序
1 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2 设定两个指针,最初位置分别为两个已经排序序列的起始位置
3 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4 重复步骤3直到某一指针超出序列尾,将另一序列剩下的所有元素直接复制到合并序列尾
// 融合两个有序数组,这里实际上是将数组 arr 分为两个数组
function mergeArray(arr, first, mid, last, temp) {
let i = first;
let m = mid;
let j = mid+1;
let n = last;
let k = 0;
while(i<=m && j<=n) {
if(arr[i] < arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while(i<=m) {
temp[k++] = arr[i++];
}
while(j<=n) {
temp[k++] = arr[j++];
}
for(let l=0; l<k; l++) {
arr[first+l] = temp[l];
}
return arr;
}
// 递归实现归并排序
function mergeSort(arr, first, last, temp) {
if(first<last) {
let mid = Math.floor((first+last)/2);
mergeSort(arr, first, mid, temp); // 左子数组有序
mergeSort(arr, mid+1, last, temp); // 右子数组有序
arr = mergeArray(arr, first, mid, last, temp);
}
return arr;
}
// example
let arr = [10, 3, 1, 5, 11, 2, 0, 6, 3];
let temp = new Array();
let SortedArr = mergeSort(arr, 0 ,arr.length-1, temp);
alert(SortedArr);
C语言: 参考:八种基本的排序(4)——归并排序(C语言实现)
//归并排序(从小到大)
#include <stdio.h>
int a[3001000]; //在主函数外定义数组
int c[3001000];
void merge_sort(int left,int right) //定义归并函数"merge_sort"
{
if ( left == right ) return; //判断是否只有一个数
int mid = ( left + right ) / 2; //取一个中间数
merge_sort(left, mid); //这两行将输入的数列强行二分
merge_sort(mid + 1,right);
int i = left; //把开始和中间的值保存在别的变量中
int j = mid + 1;
int len = 0;
while (i <= mid && j <= right) //在范围内判断前后两数的大小
{
if (a[i] < a[j]) //判断大小 大到小"<",小到大">"!!!
{
c[len] = a[i]; //如果条件成立(这里是后数比前数小)把后面的值赋到前面
len++; //表示判断过一遍
i++; //当i与下面的j其中有一个不满足上面while后的条件则跳出循环,表示排序完成
}
else
{
c[len] = a[j]; //不成立就不变
len++;
j++;
}
}
for (;i<=mid;i++) //下面几个for循环把排序好的数记录下来
{
c[len] = a[i];
len++; //挨个赋值
}
for (;j<=right;j++)
{
c[len] = a[j];
len++;
}
for (int ii = left; ii <= right ;ii++)
a[ii] = c[ii - left];
}
int main() //主函数
{
int n;
printf("输入数字个数:\n");
scanf("%d",&n); //输入要排序的数字个数
printf("输入%d个数:\n",n);
for (int i = 0 ; i < n ; i++) //循环输入
scanf("%d",&a[i]);
merge_sort(0,n-1); //调用归并排序函数"merge_sort"
for (int i = 0 ; i < n ; i++) //循环输出
{
if(i!=0) //第一个数前面不加空格
printf(" ");
printf("%d",a[i]);
}
return 0;
}
//ENDING
堆排序
1 初始化堆:将数列a[1...n]构造成最大堆。
2 交换数据:将a[1]和a[n]交换,使a[n]是a[1...n]中的最大值;然后将a[1...n-1]重新调整为最大堆。
3 接着,将a[1]和a[n-1]交换,使a[n-1]是a[1...n-1]中的最大值;然后将a[1...n-2]重新调整为最大值。
4 依次类推,直到整个数列都是有序的。
// 交换两个节点
function swap(A, i, j) {
let temp = A[i];
A[i] = A[j];
A[j] = temp;
}
// 将 i 结点以下的堆整理为大顶堆,注意这一步实现的基础实际上是:
// 假设 结点 i 以下的子堆已经是一个大顶堆,shiftDown函数实现的
// 功能是实际上是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面
// 将写一个 for 循环,从第一个非叶子结点开始,对每一个非叶子结点
// 都执行 shiftDown操作,所以就满足了结点 i 以下的子堆已经是一大
//顶堆
function shiftDown(A, i, length) {
let temp = A[i]; // 当前父节点
// j<length 的目的是对结点 i 以下的结点全部做顺序调整
for(let j = 2*i+1; j<length; j = 2*j+1) {
temp = A[i]; // 将 A[i] 取出,整个过程相当于找到 A[i] 应处于的位置
if(j+1 < length && A[j] < A[j+1]) {
j++; // 找到两个孩子中较大的一个,再与父节点比较
}
if(temp < A[j]) {
swap(A, i, j) // 如果父节点小于子节点:交换;否则跳出
i = j; // 交换后,temp 的下标变为 j
} else {
break;
}
}
}
// 堆排序
function heapSort(A) {
// 初始化大顶堆,从第一个非叶子结点开始
for(let i = Math.floor(A.length/2-1); i>=0; i--) {
shiftDown(A, i, A.length);
}
// 排序,每一次for循环找出一个当前最大值,数组长度减一
for(let i = Math.floor(A.length-1); i>0; i--) {
swap(A, 0, i); // 根节点与最后一个节点交换
shiftDown(A, 0, i); // 从根节点开始调整,并且最后一个结点已经为当
// 前最大值,不需要再参与比较,所以第三个参数
// 为 i,即比较到最后一个结点前一个即可
}
}
let Arr = [4, 6, 8, 5, 9, 1, 2, 5, 3, 2];
heapSort(Arr);
alert(Arr);
插入排序
function insertSort(arr) {
var len = arr.length;
var temp;
for (var i = 1;i < len;i++){
temp = arr[i]
for (var j = i;j > 0 && temp < arr[j-1];j--){
// 当前值和之前的每个值进行比较,发现有比当前值小的值就进行重新赋值
arr[j] = arr[j-1];
}
arr[j] = temp;
}
return arr;
}
C语言: 参考:八种基本的排序(3)——插入排序(C语言实现)
//插入排序(从小到大)
#include<stdio.h>
int number[100000000]; //在外面定义数组
void insertion_sort(int *number,int n) //定义一个插入函数"insertion_sort"
{
int i=0,ii=0,temp=0;
for(i=1;i<n;i++) //循环遍历
{
temp=number[i]; //将temp每一次赋值为number[i]
ii=i-1;
while(ii>=0&&temp<number[ii]) //这里改顺序 (temp后的)"<"为小到大,">"为大到小 !!!
{
number[ii+1]=number[ii]; //将大的元素往前放
ii--;
}
number[ii+1]=temp; //与"number[ii+1]=number[ii];"一起意为
} //如果插入的数比之前的大,将number[ii]与number[ii+1]互换
}
int main()
{
int i=0,n;
printf("输入数字个数:\n");
scanf("%d",&n); //输入要排序的数字的个数
printf("输入%d个数:\n",n);
for(int j=0;j<n;j++) //将所有数全放入number数组中
scanf("%d",&number[j]) ;
insertion_sort(number,n); //引用插入函数
for(i=0;i<n-1;i++) //循环输出
printf("%d ",number[i]); //格式需要
printf("%d\n",number[i]);
return 0;
}
//ENDING
选择排序
var arNums = [65, 52, 74, 14, -1, 2, 88];
var b = 0;
for (var i = 0; i < arNums.length; i++) {
for (var j = i+1; j < arNums.length; j++) {
if (arNums[i] > arNums[j]) {
a = arNums[i];
arNums[i] = arNums[j];
arNums[j] = a;
}
}
}
for (var i in arNums) {
console.log(i)
if (i != arNums.length - 1) {
document.write(arNums[i] + ',');
} else {
document.write(arNums[i]);
}
}
C语言: 参考:八种基本的排序(2)——直接选择排序(C语言实现)
//选择排序(从小到大排)
#include<stdio.h>
int number[100000000]; //在主函数外定义数组可以更长多了
void select_sort(int R[],int n) //定义选择排序函数"select_sort"
{
int i,j,k,index; //定义变量
for(i=0;i<n-1;i++) //遍历
{
k=i;
for(j=i+1;j<n;j++) //j初始不为0,冒泡初始为0,所以选排比冒泡快,但不稳定
{
if(R[j]<R[k]) //顺序从这里改顺序 小到大"<",大到小">" !!!
k=j; //这里是区分冒泡排序与选择排序的地方,冒泡没这句
}
if(k!=j) //为了严谨,去掉也行
{
index=R[i]; //交换R[i]与R[k]中的数
R[i]=R[k]; //简单的交换c=a,a=b,b=c
R[k]=index;
}
}
}
int main() //主程序
{
int i,n;
printf("输入数字个数:\n");
scanf("%d",&n); //输入要排序的数字的个数
printf("输入%d个数:\n",n);
for(int j=0;j<n;j++) //将所有数全放入number数组中
scanf("%d",&number[j]) ;
select_sort(number,n); //引用选择排序select_sort的函数
for (i=0;i<n-1;i++) //用for循环输出排完排完序的数组
printf("%d ",number[i]); //这样写是为了格式(最后一个数后面不能有空格)
printf("%d\n",number[i]);
return 0; //好习惯
}
//ENDING
查找
二分查找
function binary_search(arr,low, high, key) {
if (low > high){
return -1;
}
var mid = parseInt((high + low) / 2);
if(arr[mid] == key){
return mid;
}else if (arr[mid] > key){
high = mid - 1;
return binary_search(arr, low, high, key);
}else if (arr[mid] < key){
low = mid + 1;
return binary_search(arr, low, high, key);
}
};
var arr = [1,2,3,4,5,6,7,8,9,10,11,23,44,86];
var result = binary_search(arr, 0, 13, 10);
alert(result); // 9 返回目标元素的索引值
五大算法
贪心算法
局部最优算法,每一步都取当前最优。
分治算法
分成多个小模块,子问题相互独立。
动态规划
每个状态都是过去历史的一个总结。
回溯法
发现原先选择不优时,返回重新选择。
分支限界法
求解整数规划。分割子集、计算下界、剪枝超界部分
常考题纪录
大数相加相减相乘
参考资料:大数的四则运算(加法、减法、乘法、除法)
相加:两个大数用数组保存,在数组中逐位进行相加,再判断该位相加后是否需要进位。为了方便计算,将数字的低位放在数组的前面,高位放在后面。
相减:从低位开始减。先判断被减数和减数哪一个大,决定是否加负号;然后处理每一项,如果前一位相减有借位,就先减去上一位的借位,无则不减,再去判断是否能够减开被减数,如果减不开,就要借位后再去减,同时置借位为1,否则置借位为0。
相乘:一个数的第i 位和另一个数的第j 位相乘所得的数,一定是要累加到结果的第i+j 位上。这里i, j 都是从右往左,从0 开始数。ans[i+j] = a[i]*b[j];
相除:基本思想是反复做减法,看从被除数里面最多能减去多少个除数,商就是多少。一次最多能减少多少个除数的10的n次方。
以7546除以23为例:
先用7546减去23的100倍,即减去2300,可以减3次,余下646,此时商就是300 (300=100*3);
然后646减去23的10倍,即减去230,可以减2次,余下186,此时商就是320 (320=300+10*2);
然后186减去23,可以减8次,余下2,此时商就是328 (328=320+1*8);
因为2除以23的结果小于1,而我们又不用计算小数点位,所以不必再继续算下去了。
约瑟夫环
参考资料:秒懂约瑟夫环
约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。通常解决这类问题时我们把编号从0~n-1,最后结果+1即为原问题的解。
括号匹配
参考资料:LeetCode-32.最长有效括号(考察点:栈/动态规划)
解题思路:
1. 需有一个变量start记录有效括号子串的起始下标,max表示最长有效括号子串长度,初始值均为0
2. 遍历给字符串中的所有字符
2.1. 若当前字符s[index]为左括号'(',将当前字符下标index入栈(下标稍后有其他用处),处理下一字符
2.2 若当前字符s[index]为右括号')',判断当前栈是否为空
2.2.1 若栈为空,则start = index + 1,处理下一字符(当前字符右括号下标不入栈)
2.2.2 若栈不为空,则出栈(由于仅左括号入栈,则出栈元素对应的字符一定为左括号,可与当前字符右括号配对),判断栈是否为空
2.2.2.1 若栈为空,则max = max(max, index-start+1)
2.2.2.2 若栈不为空,则max = max(max, index-栈顶元素值)
大数据题
资料参考:大数据量的算法面试题