T(n) = O(f(n))
// T(n) 表示代码执行的时间;
// n 表示数据规模的大小
// f(n) 表示每行代码执行的次数总和,因为这是一个公式,所以用 f(n) 来表示
// O 表示代码的执行时间 T(n) 与 f(n) 表达式成正比
int cal(int n) { //1
int sum = 0; //2
int i = 1; //3
for (; i <= n; ++i) { //4
sum = sum + i; //5
} //6
return sum; //7
}
// 其中第 2、3 行代码都是常量级的执行时间,与 n 的大小无关,所以对于复杂度并没有影响。循环执行次数最多的是第 4、5 行代码,所以这块代码要重点分析。
// 这两行代码被执行了 n 次,所以总的时间复杂度就是 O(n)
如果 T1(n) = O(f(n)),T2(n) = O(g(n));
那么 T(n) = T1(n) + T2(n) = max(O(f(n)), O(g(n))) = O(max(f(n), g(n)))
如果 T1(n) = O(f(n)),T2(n) = O(g(n));
那么 T(n) = T1(n) * T2(n) = O(f(n)) * O(g(n)) = O(f(n) * g(n))
a[k]_address = base_address + k * data_type_size
// data_type_size 表示数组中每个元素的大小
// 下标从0开始
a[k]_address = base_address + k * data_type_size
// 下标从1开始
a[k]_address = base_address + (k - 1) * data_type_size
// data_type_size 表示数组中每个元素的大小
二维数组内存寻址:
对于 m * n 的数组,a [ i ][ j ] (i < m,j < n)的地址为:
address = base_address + (i * n + j) * data_type_size
int main(int argc, char* argv[]){
int i = 0;
int arr[3] = {0};
for(; i<=3; i++){
arr[i] = 0;
printf("hello world\n");
}
return 0;
}
// 在 C 语言中,只要不是访问受限的内存,所有的内存空间都是可以自由访问的
// 所以当i = 3 时,数组已经越界,a[3]也会被定位到某块不属于数组的内存地址上,
// 如果编译器的内存分配是递减的,那么这个地址正好是存储变量 i 的内存地址,那么 a[3]=0 就相当于 i=0,所以就会导致代码无限循环
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XYXE5sOZ-1631413187041)(https://note.youdao.com/yws/api/personal/file/3081FADEBE684E72A555E28F08A4E799?method=download&shareKey=f9123f73f012c10757490a40cbe799c7)]
p.next = x;
x.next = p.next
// 会丢失指针,因为p.next 已经指向了 x
//应该改为下面这样
x.next = p.next;
p.next = x;
int main() {
int a = 1;
int ret = 0;
int res = 0;
ret = add(3, 5);
res = a + ret;
printf("%d", res);
reuturn 0;
}
int add(int x, int y) {
int sum = 0;
sum = x + y;
return sum;
}
在用数组实现的
非循环队列中:
队满的判断条件是 tail == n,
队空的判断条件是 head == tail
循环队列:
队满的判断条件是 (tail+1)%n=head,
队空的判断条件是 head == tail
在入队前,获取tail位置,
入队时比较tail是否发生变化,如果否,则允许入队,反之,本次入队失败。
出队则是获取head位置,进行cas
function f(x) {
return g(x);
}
//每次都要保存调用信息,空间复杂度 O(n)
function fastorial(n) {
if (n === 1) {
return n;
}
return n * fastorial(n-1);
}
//只需要保留最后一个调用记录,空间复杂度 O(1)
function fastorial(n,total) {
if (n === 1) {
return total;
}
return fastorial(n-1,n*total);
}
有序元素对:a[i] <= a[j], 如果i < j。
/** 用数组实现*/
public static void bubbleSort (int[] arr) {
if (Objects.isNull(arr) || arr.length <= 1) {
return;
}
int size = arr.length;
for (int i = 0;i < size;i++) {
// for (int j = i+1;j < size;j++) {
// if (arr[i] > arr[j]) {
// int temp = arr[i];
// arr[i] = arr[j];
// arr[j] = temp;
// }
// }
//提前退出冒泡循环的标记位
boolean flag = false;
for (int j = 0;j < size - i - 1;j++) {
if (arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
//说明发生了交换
flag = true;
}
}
if (!flag) {
break;
}
}
}
/** 用链表实现*/
class Node {
int value;
Node next;
Node(int value) {
this.value = value;
}
@Override
public String toString() {
if (this.next == null) {
return String.valueOf(this.value);
}
return this.value + "->" + this.next.toString();
}
}
//方法一,交换节点的值 (一般情况下 不允许)
public Node bubbleSort1 (Node head) {
if (head == null) {
return head;
}
Node temp = head;
int len = 0;
//获取数组的长度
while (temp != null) {
temp = temp.next;
len++;
}
for (int i = 0;i < len;i++) {
Node current = head;
boolean flag = false;
for (j = 0;i < len - i - 1;j++) {
if (current.value > current.next.value) {
//交换值
int tempValue = current.value;
current.value = current.next.value;
current.next.value = temp;
flag = true;
}
current = current.next;
}
if (!flag) {
break;
}
}
return head;
}
//方法二,交换节点指针
public Node bubbleSort2 (Node head) {
if (head == null) {
return head;
}
Node temp = head;
int len = 0;
//获取链表的长度
while (temp != null) {
temp = temp.next;
len++;
}
// 缓存新链表头结点
Node newHead = head;
for (int i = 0;i < len;i++) {
//每次循环都是冒泡一次的链表
Node current = newHead;
Node pre = null;
boolean flag = false;
for (j = 0;i < len - i - 1;j++) {
if (current.value > current.next.value) {
//1、交换两个节点的引用,此时current的指针交换后会前移,只需要更新pre指向即可
//缓存下下个节点
Node tempNode = current.next.next;
//下个节点 指向当前节点 反向
current.next.next = current;
//前一个节点指向 current.next
if (pre != null) {
pre.next = current.next;
} else { //说明当前节点为头节点,那么更新头节点
newHead = current.next;
}
//前节点相应后移一位
pre = current.next;
//当前节点指向下下个节点
current.next = tempNdoe;
flag = true;
} else {
pre = current;
current = current.next;
}
}
if (!flag) {
break;
}
}
return head;
}
public static void insertionSort (int[] arr) {
if (Objects.isNull(arr) || arr.length <= 1) {
return;
}
for (int i = 1;i < arr.length;i++) {
int temp = arr[i];
int j = i - 1;
for (;j >= 0;j--) {
if (arr[j] > temp) {
//将大于temp的值,后移一位;交换数据
arr[j+1] = arr[j];
} else {
break;
}
}
//插入数据
arr[j+1] = temp;
}
}
public static void selectionSort (int[] arr) {
if (Objects.isNull(arr) || arr.length <= 1) {
return;
}
//找到未排序空间的最小值的索引
for (int i = 0;i < arr.length;i++) {
int least = i;
for (int j = i+1;j < arr.length;j++) {
if (arr[j] < arr[i]) {
least = j;
}
}
//将最小值放到排序空间的末尾(与排序空间的最后一个元素交换位置)
int temp = arr[i];
arr[i] = arr[least];
arr[least] = temp;
}
}
public static void mergeSort (int a[]) {
if (a != null) {
int len = a.length;
mergeDivide(a,0,len-1);
}
}
private static void mergeDivide (int[] a,int p,int r) {
//递归终止条件
if (p >= r) {
return;
}
// 取p到r之间的中间位置q
int q = p + (r-p)/2;
// 分治递归
mergeDivide(a,p,q);
mergeDivide(a,q+1,r);
// 合并
// merge(a,p,q,r);
merge2(a,p,q,r);
}
// 不带哨兵模式
private static void merge (int[] a,int p, int q,int r) {
//申请一个与a同样大小的临时数组
int[] temp = new int[r - p + 1];
int i = p;
int j = q+1;
int k = 0;
while (i <= q && j <= r) {
if (a[i] <= a[j]) {
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
//判断哪个子数组还有剩余的数据
int start = i;
int end = q;
if (j <= r) {
start = j;
end = r;
}
// 将剩余的数据拷贝到临时数组tmp
while (start <= end) {
temp[k++] = a[start++];
}
// 将tmp中的数组拷贝回a[p...r]
for (int m = 0;m <= r-p;m++) {
a[p+m] = temp[m];
}
}
// 哨兵模式
private static void merge2 (int[] a,int p, int q,int r) {
//申请左右两个临时数组
int[] left = new int[q - p + 2];
int[] right = new int[r - q + 1];
int leftMax = left.length - 1;
int rightMax = right.length - 1;
for (int i = 0;i < leftMax;i++) {
left[i] = a[p+i];
}
for (int i = 0;i < rightMax;i++) {
right[i] = a[q+1+i];
}
left[leftMax] = Integer.MAX_VALUE;
right[rightMax] = Integer.MAX_VALUE;
//比较大小,把有序数据放回原数组 利用哨兵简化,如果左右部分的最后一个元素都是最大且相等,左边结束时右边也已经结束
//不带哨兵的递归是比较过后,把有序数据放入temp数组
int i = 0;
int j = 0;
int k = p;
while (k <= r) {
if (left[i] < right[j]) {
a[k++] = left[i++];
} else {
a[k++] = right[j++];
}
}
}
public static void quickSort(int[] a,int left,int right) {
if (left >= right) {
return;
}
//进行分区,并返回分区后中心点的索引值
int middle = partition(a,left,right);
//递归 分区
quickSort(a,left,middle-1);
quickSort(a,middle + 1,right);
}
private static int partition (int[] a,int left,int right) {
//选择最后一个为分区点
int pivot = a[right];
//i-1为已处理区间 -- 比pivot小
int i = left;
//j为未处理区间,从其中找出比pivot小的数值,与已处理区间末尾的数据(比pivot大)交换
for (int j = left;j < right;j++) {
if (a[j] < pivot) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
i++;
}
}
//将pivot 放到 中间 (已处理区间末尾)
int temp = a[i];
a[i] = pivot;
a[right] = temp;
return i;
}
假设待排序的数组,arr={5, 3, 7, 1, 8, 2, 9, 4, 7, 2, 6, 6, 2, 6, 6}
很容易发现,待排序的元素在[0, 10]之间,可以用counting[0,10]来存储计数
public int binarySearch (int[] a,int val) {
int start = 0;
int end = a.length - 1;
while (start <= end) {
int mid = start + ((end-start) >> 1); // start + (end - start) / 2
if (a[mid] > val) {
end = mid - 1;
} else if (a[mid] < val) {
start = mid + 1;
} else {
return mid;
}
}
return -1;
}
public int binarySearch (int[] a,int val) {
return subBinarySearch(a,val,0,a.length - 1);
}
private int subBinarySearch (int[] a,int val,int start,int end) {
if (start > end) {
return -1;
}
int mid = start + ((end - start) >> 1);
if (a[mid] > val) {
end = mid -1;
} else if (a[mid] < val) {
start = mid + 1;
} else {
return mid;
}
return subBinarySearch(a,val,start,end);
}
public int binarySearch (int[] a,int val) {
int start = 0;
int end = a.length - 1;
while (start <= end) {
int mid = start + ((end-start) >> 1); // start + (end - start) / 2
if (a[mid] > val) {
end = mid - 1;
} else if (a[mid] < val) {
start = mid + 1;
} else {
if ((mid == 0) || (a[mid-1]) != val) {
return mid;
} else {
end = mid - 1;
}
}
}
return -1;
}
public int binarySearch (int[] a,int val) {
int start = 0;
int end = a.length - 1;
while (start <= end) {
int mid = start + ((end-start) >> 1); // start + (end - start) / 2
if (a[mid] > val) {
end = mid - 1;
} else if (a[mid] < val) {
start = mid + 1;
} else {
if ((mid == a.length - 1) || (a[mid+1]) != val) {
return mid;
} else {
start = mid + 1;
}
}
}
return -1;
}
public int binarySearch (int[] a,int val) {
int start = 0;
int end = a.length - 1;
while (start <= end) {
int mid = start + ((end-start) >> 1); // start + (end - start) / 2
if (a[mid] >= val) {
if ((mid == 0) || (a[mid-1]) < val) {
return mid;
} else {
end = mid - 1;
}
} else {
start = mid + 1;
}
}
return -1;
}
public int binarySearch (int[] a,int val) {
int start = 0;
int end = a.length - 1;
while (start <= end) {
int mid = start + ((end-start) >> 1); // start + (end - start) / 2
if (a[mid] <= val) {
if ((mid == a.length - 1) || (a[mid+1]) > val) {
return mid;
} else {
start = mid + 1;
}
} else {
end = mid - 1;
}
}
return -1;
}
//precision 精确度,6位的话,就是0.000001
public static double sqrt (double num,double precision) {
if (num < 0) {
return Double.NaN;
}
double low = 0;
double up = num;
if (num > 0 && num < 1) {
low = num;
up = 1;
}
double mid = low + (up - low) / 2;
while (up - low > precision) {
if (mid > num / mid) { //mid * mid > num 避免溢出,可以写为 mid > num / mid
up = mid;
} else if (mid < num / mid) {
low = mid;
} else {
return mid;
}
mid = low + (up - low) / 2;
}
return up;
}
class Solution {
public int search(int[] nums, int target) {
if (null == nums || nums.length == 0) {
return -1;
}
if (nums.length == 1) {
if (nums[0] == target) {
return 0;
}
return -1;
}
int low = 0;
int up = nums.length - 1;
int index = getIndex(nums,low,up);
if (index != -1) {
int val = binarySearch(nums,target,low,index);
if (val != -1) {
return val;
}
return binarySearch(nums,target,index + 1,up);
}
return binarySearch(nums,target,low,up);
}
//查找到循环数组的中间点
private int getIndex (int[] a,int low,int up) {
if(a.length < 1) {
return -1;
}
while (low <= up) {
int mid = low + ((up - low) >> 1);
if (a[mid] > a[mid+1]) {
return mid;
} else if (a[mid] < a[low]) {
up = mid;
} else if (a[mid] > a[up]) {
low = mid;
} else {
return -1;
}
}
return -1;
}
//二分查找
private int binarySearch (int[] a,int val,int low,int up) {
while (low <= up) {
int mid = low + ((up - low) >> 1);
if (a[mid] > val) {
up = mid - 1;
} else if (a[mid] < val) {
low = mid + 1;
} else {
return mid;
}
}
return -1;
}
}
//伪代码实现
//递归
//递推公式
preOrder(r) = print r->preOrder(r->left)->preOrder(r->right)
void preOrder(Node* root) {
if (root == null) {
return;
}
print root; // 此处为伪代码,表示打印root节点
preOrder(root->left);
preOrder(root->right);
}
//伪代码实现
//递归
//递推公式
inOrder(r) = inOrder(r->left)->print r->inOrder(r-right)
void inOrder(Node* root) {
if (root == null) {
return;
}
inOrder(root->left);
print root; // 此处为伪代码,表示打印root节点
inOrder(root->right);
}
//伪代码实现
//递归
//递推公式
postOrder(r) = postOrder(r->left)->postOrder(r-right)->print r
void inOrder(Node* root) {
if (root == null) {
return;
}
postOrder(root->left);
postOrder(root->right);
print root; // 此处为伪代码,表示打印root节点
}
Java代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List> levelOrder(TreeNode root) {
if (root == null) return new ArrayList<>(0);
List> result = new ArrayList<>();
Queue queue = new LinkedList();
queue.offer(root);
Queue curLevelNodes = new LinkedList();
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
curLevelNodes.offer(node);
if (queue.isEmpty()) {
List list = new ArrayList<>(curLevelNodes.size());
while (!curLevelNodes.isEmpty()) {
TreeNode curNode = curLevelNodes.poll();
list.add(curNode.val);
if (curNode.left != null) {
queue.offer(curNode.left);
}
if (curNode.right != null) {
queue.offer(curNode.right);
}
}
result.add(list);
}
}
return result;
}
}
public static class Node {
private int data;
private Node left;
private Node right;
public Node (int data) {
this.data = data;
}
}
public class BinarySearchTree {
// tree 为根节点
public Node find (Node tree,int data) {
Node p = tree;
while (p != null) {
if (data < p.data) {
p = p.left;
} else if (data > p.data) {
p = p.right;
} else {
return p;
}
}
return null;
}
}
public static class Node {
private int data;
private Node left;
private Node right;
public Node (int data) {
this.data = data;
}
}
public class BinarySearchTree {
// tree 为根节点
public void insert (Node tree,int data) {
if (tree == null) {
tree = new Node(data);
return;
}
Node p = tree;
while (p != null) {
if (data < p.data) {
if (p.left == null) {
p.left = new Node(data);
return;
}
p = p.left;
} else if (data > p.data) {
if (p.right == null) {
p.right = new Node(data);
return;
}
p = p.right;
}
}
}
}
public static class Node {
private int data;
private Node left;
private Node right;
public Node (int data) {
this.data = data;
}
// tree 为根节点
public void delete (Node tree,int data) {
//第一步:首先找出要删除的节点
//p 指向要删除的节点,初始化指向根节点
Node p = tree;
//记录P的父节点,初始化为null
Node pp = null;
while (p != null && p.data != data) {
pp = p;
if (data > p.data) {
p = p.right
} else {
p = p.left;
}
}
if (p == null) {
return; //没有找到
}
//要删除的节点有两个子节点
//查找右子树中的最小节点
if (p.left != null && p.right != null) {
Node minP = p.right;
Node minPP = p; // minPP表示minP的父节点
while (minP.left != null) {
minPP = minP;
minP = p.left;
}
// 将minP的数据替换到p中
p.data = minP.data;
// 下面就变成了删除minP了
p = minP;
pp = minPP;
}
//第二步:开始删除找到的节点
//如果删除的节点是叶子节点,或者 仅有一个子节点
Node child; //p 的子节点
if (p.left != null) {
child = p.left;
} else if (p.right != null) {
child = p.right;
} else {
child = null;
}
if(pp == null) {
tree = child; //如果删除的是根节点
} else if (pp.left == p) {
pp.left = child;
} else {
pp.right = child;
}
}
}
数据结构 | 优点 | 缺点 | 应用场景 |
---|---|---|---|
哈希表 | 插入删除查找都是O(1), 是最常用的 | 哈希冲突,不能顺序遍历以及扩容缩容的性能损耗 | 那些不需要顺序遍历,海量数据随机访问、防止重复、缓存等 |
跳表 | 插入删除查找都是O(logn), 并且能顺序遍历,区间查找非常方便(基于链表),支持多写多读 | 空间复杂度O(n) | 不那么在意内存空间的 |
红黑树 | 插入删除查找都是O(logn), 中序遍历即是顺序遍历,稳定 | 难以实现,去查找不方便 | TreeMap、TreeSet、HashMap等 |
public class Heap {
private int[] a; //数组,从下标1开始存储数据
private int capacity; //堆的容量
private int count; //堆中已存储的数据个数
public Heap (int capacity) {
a = new int[capacity + 1];
this.capacity = capacity;
count = 0;
}
public void insert (int data) {
if (count >= capacity) { //堆满了,无法添加数据
return;
}
count++;
a[count] = data;
int i = count;
//自下往上堆化
while (i/2 > 0 && a[i] > a[i/2]) { // i/2 为i这个节点的父节点
//交互节点i 与其 父节点的值
swap(a,i,i/2);
i = i/2;
}
}
private void swap (int[] a,int before,int after) {
if (a == null || before >= a.length || after >= a.length) {
return;
}
int temp = a[before];
a[before] = a[after];
a[after] = temp;
}
}
public class Heap {
private int[] a; //数组,从下标1开始存储数据
private int capacity; //堆的容量
private int count; //堆中已存储的数据个数
public Heap (int capacity) {
a = new int[capacity + 1];
this.capacity = capacity;
count = 0;
}
public void remove () {
if (count == 0) { //堆中没数据
return;
}
//自上往下堆化
a[1] = a[count];
count--;
int i = 1;
while (true) {
int maxPos = i; //默认i位置为最大元素的位置
//如果最大元素小于当前节点的左子节点,那么最大元素的位置为左子节点位置
if (i * 2 <= count && a[i] < a[2*i]) {
maxPos = i*2;
}
//如果最大元素小于当前节点的右子节点,那么最大元素的位置为右子节点位置
if (i * 2 + 1 <= count && a[maxPos] < a[2*i + 1]) {
maxPos = i*2 + 1;
}
//如果最大元素为当前节点,就跳出循环
if (maxPos == i) {
break;
}
//交互当前节点和最大节点的值
swap(a,i,maxPos);
//将当前节点赋值为最大节点
i = maxPos;
}
}
private void swap (int[] a,int before,int after) {
if (a == null || before >= a.length || after >= a.length) {
return;
}
int temp = a[before];
a[before] = a[after];
a[after] = temp;
}
}
private static void buildHeap(int[] a, int n) {
for (int i = n/2; i >= 1; --i) {
heapify(a, n, i);
}
}
private static void heapify(int[] a, int n, int i) {
while (true) {
int maxPos = i;
if (i*2 <= n && a[i] < a[i*2]) maxPos = i*2;
if (i*2+1 <= n && a[maxPos] < a[i*2+1]) maxPos = i*2+1;
if (maxPos == i) break;
swap(a, i, maxPos);
i = maxPos;
}
}
// n表示数据的个数,数组a中的数据从下标1到n的位置。
public static void sort(int[] a, int n) {
buildHeap(a, n); //堆化 见上面代码
int k = n;
while (k > 1) {
swap(a, 1, k);
--k;
heapify(a, k, 1); //从上往下堆化
}
}
public class Graph { // 无向图
private int v; // 顶点的个数
private LinkedList adj[]; // 邻接表
public Graph(int v) {
this.v = v;
adj = new LinkedList[v];
for (int i=0; i();
}
}
public void addEdge(int s, int t) { // 无向图一条边存两次
adj[s].add(t);
adj[t].add(s);
}
}
public class EightQueens {
//全局或成员变量,下标表示行,值表示queen存储的列
int[] result = new int[8];
public void cal8Queens (int row) {
if (row == 8) {// 8个棋子都放置好了,打印结果
printQueens(result);
return; // 8行棋子都放好了,已经没法再往下递归了,所以就return
}
for (int column = 0;column < 8;column++) {
if (isOk(row,column)) {//如果已经放好
result[row] = column;// 第row行的棋子放到了column列
cal8Queens(row+1); // 递归考察下一行
}
}
}
//判断 row 行 column 列放置是否合适
private boolean isOk (int row,int column) {
int leftup = column - 1; //左上角对角线
int rightup = column + 1; //右上角对角线
for (int i = row -1;i >= 0;i--) { //逐行往上考察每一行
if (result[i] == column) {// 第i行的column列有棋子吗?
return false;
}
if (leftup >= 0) { // 考察左上对角线:
if (result[i] == leftup) { //第i行leftup列有棋子吗?
return false;
}
}
if (rightup < 8) { // 考察右上对角线
if (result[i] == rightup) { // 第i行rightup列有棋子吗?
return false;
}
}
leftup--;
rightup++;
}
return true;
}
private void printQueens(int[] result) { // 打印出一个二维矩阵
for (int row = 0; row < 8; ++row) {
for (int column = 0; column < 8; ++column) {
if (result[row] == column) System.out.print("Q ");
else System.out.print("* ");
}
System.out.println();
}
System.out.println();
}
}
public class BagQuestion {
//存储背包中物品总重量的最大值
public int maxW = Integer.MiN_VALUE;
// countWeight表示当前已经装进去的物品的重量和;i表示考察到哪个物品了;
// bagWeight表示背包最大乘重;items表示物品的重量数组;n表示物品个数
// 假设背包可承受重量100,物品个数10,物品重量存储在数组a中,那可以这样调用函数:
// bugFunction(0, 0, a, 10, 100)
public void bugFunction (int i,int countWeight,int[] items,int n,int bagWeight) {
//如果装满了 或者 已经考察完所有物品,则结束
if (countWeight == bagWeight || i == n) {
if (countWeight > maxW) {
maxW = countWeight;
}
return;
}
//下个物品不放进背包,即不考查
bugFunction(i+1,countWeight,items,n,bagWeight);
//将下个物品放进背包
// 已经装好的物品总量不能超过背包的承受重量
if (countWeight + item[i] <= bagWeight) {
bugFunction(i+1,countWeight + item[i],items,n,bagWeight);
}
}
}