时间复杂度
- 算法的时间复杂度是一个函数,它定性的描述了该算法的运行时间。常用O()表示(最坏情况复杂度),另外还有"Ω"(最好情况复杂度)、“θ” (平均时间复杂度),
- 在函数表达式中,只看高阶项,不看低阶项,也不看高阶项的系数
常规的位运算
- ^(异或):位与位比较,相同为0,不同为1,例如:10010 ^ 10101 = 00111
- 一个数异或自身结果0,一个数异或0结果为自身
- 异或操作满足交换律和结合律
- ~(非):按位取反
- &(且):位与位比较,相同为1,不同为0
- |(或):位与位比较,有一个为1则结果为1
- >>(右移):数字右移,右移一位相当于除以二,例如:1010 >> 1 => 0101
- <<(左移):数字左移,左移一位相当于乘以二,例如:1010 << 1 => 10100
排序算法
1. 选择排序、冒泡排序详解
- 时间复杂度O(N^2),额外空间复杂度O(1)
- 选择排序:
<script>
function selectSort(arr) {
if (arr.length < 2) return
for (let i = 0; i < arr.length - 1; i++) {
let minIndex = i
for (let j = i + 1; j < arr.length - 1; j++) {
minIndex = arr[minIndex] > arr[j] ? j : minIndex
}
swap(arr, minIndex, i)
}
}
</script>
- 冒泡排序:
<script>
function bubbleSort(arr) {
if (arr == [] || arr.length < 2) return
for (let i = arr.length; i > 0; i--) {
for (let j = 0; j < i; j++) {
if(arr[j] > arr[j+1])swap(arr, j, j+1)
}
}
}
</script>
- 交换数组中两个数的位置
// 常规方式,需要额外一个变量
function swap(arr, index1, index2) {
let tmp = arr[index1]
arr[index1] = arr[index2]
arr[index2] = tmp
}
// 采用异或的方式
function swap(arr, index1, index2) {
arr[index1] = arr[index1] ^ arr[index2]
arr[index2] = arr[index1] ^ arr[index2]
arr[index1] = arr[index1] ^ arr[index2]
}
2. 插入排序详解
- 时间复杂度O(N^2),额外空间复杂度O(1)
- 插入排序
function insertSort(arr) {
if (arr == [] || arr.length < 2) return
for (let i = arr.length - 1; i >= 0; i--) {
for (let j = i-1; j >= 0 && arr[j] < arr[j-1]; j++) {
swap(arr, j, j-1)
}
}
}
3. 归并排序详解
function process(arr, left, right){
if(left == right)return
let mid = Math.floor((left + right) / 2)
process(arr, left, mid)
process(arr, mid + 1, right)
merge(arr, left, mid, right)
}
function merge(arr, left, mid, right){
let help = []
let i = 0, p1 = left, p2 = mid + 1
while(p1 <= mid && p2 <= right){
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]
}
while(p1 <= mid){
help[i++] = arr[p1++]
}
while(p2 <= right){
help[i++] = arr[p2++]
}
for(i = 0; i < help.length; i++){
arr[left+i] = help[i]
}
}
- 归并排序时间复杂度可以采用递归master公式求得,T(N) = a * T(N/b) + O(N^d)
- 可以得出其中a = 2, b = 2, d = 1,log(a, b) = d,满足第二个条件,所以时间复杂度为O(NlogN)
利用归并排序可以延伸出小和问题,在一个数组中每一个元素的左边比他小的数之和称为小和
let arr = [1, 3, 4, 2, 5]
let min = 0
process(arr, 0, arr.length - 1)
function process(arr, left, right){
if(left == right)return
let mid = Math.floor((left + right) / 2)
process(arr, left, mid)
process(arr, mid + 1, right)
merge(arr, left, mid, right)
}
function merge(arr, left, mid, right){
let help = []
let i = 0, p1 = left, p2 = mid + 1
while(p1 <= mid && p2 <= right){
if(arr[p1] <= arr[p2])min += arr[p1] * (right-p2+1)
help[i++] = arr[p1] <= arr[p2] ? arr[p1++]: arr[p2++]
}
while(p1 <= mid){
help[i++] = arr[p1++]
}
while(p2 <= right){
help[i++] = arr[p2++]
}
for(i = 0; i < help.length; i++){
arr[left+i] = help[i]
}
}
console.log('小和为:',min)
其他算法
1. 二分查找
function process(arr, num){
let left = 0, right = arr.length - 1
let mid = Math.round((left + right) / 2)
while(arr[mid] != num){
if(arr[mid] > num)right = mid - 1
else if(arr[mid] < num)left = mid + 1
mid = Math.round((left + right) / 2)
}
if(left > right)console.log('未找到')
console.log('num位于', mid)
}
2. 二分递归求范围最大值
function getMax(arr, left, right){
if(left == right - 1 || left == right)return Math.max(arr[left], arr[right])
let mid = (left + right) / 2
let leftMax = getMax(arr, left, mid)
let rightMax = getMax(arr, mid + 1, right)
return Math.max(leftMax, rightMax)
}
对于递归算法来说,时间复杂度可以采用master公式计算
公式:T(N) = a * T(N/b) + O(N^d)
- log(b,a) > d ===> 复杂度为O(N^log(b,a))
- log(b,a) = d ===> 复杂度为O(N^d * logN)
- log(b,a) < d ===> 复杂度为O(N^d)
其中a表示调用子递归的次数,T(N/b)表示子问题的规模,O(N^d)表示除递归外其他操作的时间复杂度