目录
基本知识
常数操作:一个操作和样本的数据量没有关系,每次都是固定时间内完成
时间复杂度:列出常数操作的数量的表达式,舍去低阶项,只看最高阶项,且忽略最高阶项的系数
O(n)中的O是类似上限的意思,默认数据量大到一定规模
算法流程优劣:先看时间复杂度(按最坏情况估计时间复杂度),再分析不同数据样本下的实际运行时间(常数项时间)
算法流程优化:数据状况+问题
题目一
选择排序( 每次找到最小,和第i++位交换 )
冒泡排序( 两两比较,将较大者后移 )
时间复杂度→O(n^2)
额外空间复杂度→O(1)
plus:相邻两数交换(异或/无进位相加):
i = i ^ j; j = i ^ j; i = i ^ j;
题目二
插入排序
( i = 1,将 0→i 位不断排序,i++)
插入排序的时间复杂度会受数据影响(0→i 位排序的过程中);而选择、冒泡排序不会受影响
题目三
二分法 / 折半
( mid = (high - low)/2;再调整 low 和 high 以得出mid, 重复 )
时间复杂度:O(logn),算法中默认将O(log2n)写成O(logn)
(一直二分到最后)
降序序列中:找出 ≤b 的最左侧的位置 / 找出 ≥b 的最右侧的位置
升序序列中:找出 ≥ a的最左侧的位置 / 找出≤a 的最右侧的位置
查找局部最小值
局部最小值定义:if 是第一个元素,即需<第二个元素;
if 是最后一个元素,即需<倒数第二个元素;
else 即需 前一个 ≤ 元素 ≤后一个。
题:如果一个数组,相邻的元素不相等,求局部最小
题目四
异或求出现奇数次元素
数组中:一种数(a)奇数次,其他都是偶数次,找出奇数次的数
解:全部异或,console = 0^arr[0]^arr[1]^……arr[n] = 0 ^ arr[i] = a
数组中:两种数(a、b)出现奇数次,其他偶数次,找出这两种数
解:全部异或,console = a ^ b ;再筛选元素异或得a;再console ^ a 得 b
题目五
递归行为
时间复杂度——master公式(等分)
得到时间复杂度为:if logba < d → O(N^d)
if logba == d →O(N^d * logN)
if logba > d →O(N^(logba))
eg:查找arr[ i , j ]范围内的最大值(递归非最优,仅仅举例)
class getMax_digui { //递归函数举例 static void Main(string[] args) { Console.WriteLine("递归法找范围内最大值"); //test int maxSize = 20; int maxValue = 20; int[] arr1 = generateRandomArray(maxSize, maxValue); int num = getMax(arr1); foreach (int i in arr1) { Console.Write(i + " "); } Console.WriteLine(); Console.WriteLine(num); } public static int getMax(int[] arr) { return process(arr, 0, arr.Length - 1); } public static int process(int[] arr,int l,int r) { //当l→r范围只有一个数时,直接返回 if(l == r) { return arr[l]; } //右移一位执行速度比除以二快 int mid = l + ((r - l) >> 1); int lMax = process(arr, l, mid); int rMax = process(arr, mid + 1, r); return Math.Max(lMax, rMax); } //对数器——样本生成器 public static int[] generateRandomArray(int maxSize,int maxValue) { //长度随机:[ 0,maxSize ] 所有的整数,随机返回一个 Random rd = new Random(); int[] arr = new int[rd.Next(0, maxSize + 1)]; for (int i = 0; i < arr.Length; i++ ) { arr[i] = rd.Next(0, maxValue + 1) - rd.Next(0, maxValue); } return arr; } }
题目六
归并排序( 左右排序+merge拷贝 )
额外空间复杂度:O(N)
时间复杂度:O(N*logN),它的比较信息向下传递,比较行为没有浪费
namespace sf2
{
class guiBing_digui
{
static void Main(string[] args)
{
Console.WriteLine("归并排序");
//test
int maxSize = 20;
int maxValue = 20;
int[] arr1 = generateRandomArray(maxSize, maxValue);
guiBing(arr1);
//显示数组
foreach (int i in arr1)
{
Console.Write(i + " ");
}
Console.WriteLine();
}
public static void guiBing(int[] array)
{
if (array == null || array.Length < 2)
{
return;
}
process(array, 0, array.Length - 1);
}
public static void process(int[] array, int l, int r)
{
if (l == r)
{
return ;
}
int mid = l + ((r - l) >> 1);
process(array, l, mid);
process(array, mid + 1, r);
merge(array, l, mid, r);
}
public static void merge(int[] arr, int l, int mid, int r)
{
int[] ex = new int[r - l + 1];
int p1 = l;
int p2 = mid + 1;
int i = 0;
while (p1 <= mid && p2 <= r)
{
//双指针
ex[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid)
{
ex[i++] = arr[p1++];
}
while (p2 <= r)
{
ex[i++] = arr[p2++];
}
for (i = 0; i < ex.Length; i++)
{
arr[l + i] = ex[i];
}
}
//对数器——样本生成器
public static int[] generateRandomArray(int maxSize, int maxValue)
{
//长度随机:[ 0,maxSize ] 所有的整数,随机返回一个
Random rd = new Random();
Random ee = new Random();
int[] arr = new int[rd.Next(maxSize + 1)];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = rd.Next(maxValue + 1) - rd.Next(maxValue);
}
return arr;
}
}
}
小和问题
元素的小和——当前元素之前的所有比它小的元素之和
数组的小和——所有元素的小和之和
( 逆序问题同理反之 )
解:相当于从每个子程序的右半部分中找出大于左半部分第i++位的数有几个
namespace sf1 { class minSum_digui { static void Main(string[] args) { Console.WriteLine("递归法求小和"); //test int maxSize = 20; int maxValue = 20; int[] arr1 = generateRandomArray(maxSize, maxValue); //显示数组 foreach (int i in arr1) { Console.Write(i + " "); } int Sum = minSum(arr1); Console.WriteLine(); Console.WriteLine(Sum); } public static int minSum(int[] array) { if (array == null || array.Length < 2) { return 0; } return process(array, 0, array.Length -1); } public static int process(int[] array, int l, int r) { if(l == r) { return 0; } int mid = l + ((r - l) >> 1); return process(array, l, mid) + process(array, mid +1 , r) + merge(array, l, mid, r); } public static int merge(int[] arr, int l,int mid,int r) { int[] ex = new int[r - l + 1]; int num = 0; int p1 = l; int p2 = mid + 1 ; int i = 0; while (p1 <= mid && p2 <= r ) { //反向思考——计算有多少个大于P1的P2 num += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0; //当p1和p2相等时,先将p2存入,以保证p1和后续p2++的正常比较 ex[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } while(p1 <= mid) { ex[i++] = arr[p1++]; } while(p2 <= r) { ex[i++] = arr[p2++]; } for(i = 0;i< ex.Length; i++) { //将排好序的数组替换进去,以保证上层比较的正常进行 arr[l + i] = ex[i]; } return num; } //对数器——样本生成器 public static int[] generateRandomArray(int maxSize, int maxValue) { //长度随机:[ 0,maxSize ] 所有的整数,随机返回一个 Random rd = new Random(); Random ee = new Random(); int[] arr = new int[rd.Next(maxSize + 1)]; for (int i = 0; i < arr.Length; i++) { arr[i] = rd.Next(maxValue + 1) - rd.Next(maxValue); } return arr; } } }
题目七
以下快排前两个版本时间复杂度都是:O(N^2)
快速排序 1.0版( 数组分区 )
( 划分(partition)为两部分 )
(1) 已知一个数组array,一个数num。让array中 ≤num 的数在左侧;>num 的数在右侧。
快速排序 2.0版( 荷兰国旗 )
( 划分(partition)为三部分 )
(2) 荷兰国旗 (将数组分成<num;=num;>num 三块)(也可双指针解)
arr[i] < num,[i]和小于边界的+1元素作交换,小于边界++,i++;
arr[i] == num,i++;
arr[i] > num,[i]和大于边界的-1元素作交换,大于边界--,i不变( 避免漏掉 )
快速排序 3.0版本
( 从数组中间选取一个数当作num,以避免最差情况造成O(n^2) )
时间复杂度:O( N* logN )
namespace QuikSort
{
//改进后的最优版快排3
class QuiSort3
{
static void Main(string[] args)
{
//简单测试
int[] arr = { 2,0,4,3,7,10,0,6 };
QuickSort(arr);
for(int i = 0;i< arr.Length; i++)
{
Console.Write(arr[i] + " ");
}
}
public static void QuickSort(int[] arr)
{
if(arr == null || arr.Length < 2)
{
return;
}
QuickSort(arr, 0, arr.Length - 1);
}
/// <summary>
/// 快速排序(逐层partition)
/// </summary>
/// <param name="arr">数组</param>
/// <param name="l">第一个元素下标</param>
/// <param name="r">最后一个元素下标</param>
public static void QuickSort(int[] arr,int l,int r)
{
Random ran = new Random();
if(l < r)
{
//从数组随机选取一个判断值,赋给arr[r]
Swap(arr, l + (int)ran.Next(r - l + 1), r);
int[] par = Partition(arr, l, r);
QuickSort(arr, l, par[0]); //小于区
QuickSort(arr, par[1] + 1, r); //大于区
}
}
/// <summary>
/// 按照指定数字分小于(含等于)区和大于区
/// </summary>
/// <param name="arr">数组</param>
/// <param name="l">左边界</param>
/// <param name="r">指定的判定数字</param>
/// <returns></returns>
public static int[] Partition(int[] arr,int l,int r)
{
//小于r区域的边界
int less = l - 1;
//大于r区域的边界
int more = r ;
while (l < more)
{
if(arr[l]< arr[r])
{
Swap(arr, l++, ++less);
}
else if(arr[l] > arr[r])
{
Swap(arr, l, --more);
}
else
{
l++;
}
}
//将判定数放在大于区中
Swap(arr, more, r);
return new int[] { less, more };
}
//交换数组元素值
public static void Swap(int[] arr,int a,int b)
{
int sheep = 0;
sheep = arr[a];
arr[a] = arr[b];
arr[b] = sheep;
}
}
}
Plus:右移一位( >>1 )的执行速度快于除以二运算
对数器——随机样本产生器
自写对数器,随机产生样本,对函数结果进行测试,观其是否能产生正确结果。代替线上OJ