PAT甲级考试知识点总结和一些看法

news2024/9/21 22:43:44

0 引言

本人今年PAT甲级考了95分,平时力扣也有再刷(大概有360题),感觉PAT主要还是面向考研党的,里面的题目其实难度是小于力扣的,但这种难度的题目浙大去年考研机试居然有20%的0分我其实不是很理解。

PAT主要是考察编码能力(一道题写个一两百行太正常不过了)和数据结构基础,对于算法只会考察一些比较经典的算法比如排序算法啥的,而力扣的题目算法考察相对会多一点,而且PAT的题目不会告诉你测试样例,输入输出也很恶心,总会在意想不到的地方恶心你一下,所以我个人认为对于找工作党如果力扣的投入产出比是1:1,那么PAT就是1:0.5。

PAT的签约企业主要是一些外企和顶级大厂比如谷歌、微软、阿里、腾讯啥的,而且基本要求甲级满分,其实感觉PAT满分不容易的因为他总有几个点会恶心你,关键的是他还不告诉你测试样例(被恶心坏了),除了这些大厂其他签约企业基本都是在杭州,所以如果是为了找工作并且不打算来杭州发展感觉PAT确实没啥用(当然如果你是为了提升编程能力和数据结构基础,还是很推荐的~)

下面推荐一些书籍、博客、视频教程和我总结归纳的一些考点

书籍:

《大话数据结构》:还是挺适合入门的,但其实写得还是比较浅,应付PAT甲级还是不太够
《数据结构与算法分析》:都打算学数据结构与算法了,大名鼎鼎的黑皮书,你说要不要看麻~

博客:

我推荐柳神!https://www.liuchuo.net/

视频教程:

我推荐y总的PAT甲级辅导课不过有点小贵,量力而行吧~

https://www.acwing.com/activity/content/27/

在这里插入图片描述

1 STL

1.1 vector

动态数组

  • size:大小
  • push_back:向数组末尾插入一个元素
  • pop_back:向数组末尾弹出一个元素
  • resize:重新定义数组大小

1.2 unordered_map

哈希表

  • size:大小
  • [ ]:赋值与取值

1.3 unordered_set

哈希集合

  • size:大小
  • insert:插入一个元素
  • erase:删除一个元素
  • count:某个元素在集合中的数量

1.4 stack

  • push:向栈顶插入一个元素
  • pop:弹出栈顶元素
  • size:大小
  • top:获取栈顶元素

1.5 queue

队列

  • push:向队尾插入元素
  • pop:队首弹出一个元素
  • front:获取队首元素
  • size:大小

1.6 deque

双端队列

  • size:大小
  • push_back:队尾插入元素
  • pop_back:队尾弹出元素
  • push_front:队首插入元素
  • pop_front:队首弹出元素
  • front:获取队首元素
  • back:获取队尾元素

1.7 priority_queue

优先队列(堆)

  • push:插入一个元素
  • pop:删除堆顶元素
  • top:获取堆顶元素
  • size:大小

大顶堆:prority_queue<int,vector<int>,less<int>>

小顶堆:prority_queue<int,vector<int>,greater<int>>

2 基础数据结构及其算法

2.1 栈

stack

2.1.1 单调栈

给定一个长度为 NN 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。

    stack<int> s;
    while(n--) {
        int x;
        cin >> x;
        while(!s.empty() && s.top() >= x) s.pop();
        if(s.empty()) cout << -1 << ' ';
        else cout << s.top() << ' ';
        s.push(x);
    }

2.2 队列

queue

2.2.1 单调队列

最典型的就是滑动窗口问题了

/**
* 使用单调队列,保证这个队列是一个单调递减的队列另一个单调递增
*/
void maxSlidingWindow(vector<int>& nums, int k) {
    deque<int> q;
    deque<int> q2;
    int pre = nums[0];
    for(int i = 0; i < nums.size(); i++) {
        if(i >= k && pre == q.front()) q.pop_front();
        if(i >= k && pre == q2.front()) q2.pop_front();
        while(!q.empty() && q.back() < nums[i]) q.pop_back();
        while(!q2.empty() && q2.back() > nums[i]) q2.pop_back();
        q.push_back(nums[i]);
        q2.push_back(nums[i]);
        if(i >= k-1) {
            res1.push_back(q.front());
            res2.push_back(q2.front());
            pre = nums[i-k+1]; 
        }
    }
}

2.3 链表

数组模拟链表

链表如果使用指针的方式比较慢会超时,所以一般会用数组模拟链表

vector<int> LinkedList;
//尾插法
void add(int x) {
	LinkedList.push_back(x);
}

2.4 堆

prority_queue

2.4.1 down

const int N = 110;
int heap[N];//小顶堆
int n;//堆的大小
void down(int u) {
	int t = u;
	if (2 * u <= n && heap[2 * u] < heap[t]) t = 2 * u;
	if (2 * u + 1 <= n && heap[2 * u + 1] < heap[t]) t = 2 * u + 1;
	if (t != u) {
		swap(heap[u], heap[t]);
		down(t);
	}
}

2.4.2 up

const int N = 110;
int heap[N];//小顶堆
int n;//堆的大小

void up(int u) {
	while (u / 2 && heap[u / 2] > heap[u]) {
		swap(heap[u / 2], heap[u]);
		u = u / 2;
	}
}

2.5 树

树也一般不能使用链表,也是使用数组来模拟的

2.5.1 二叉树

const int N = 110;
struct Node
{
	int data, left = -1, right = -1;
};

vector<Node> BinaryTree(N);

一般直接以data为key放到数组的相应位置则表示插入节点,-1表示指向NULL

有时N比较大所以可能会爆内存故采用unordered_map来节省空间

const int N = 110;
struct Node
{
	int data, left = -1, right = -1;
};

unordered_map<int, Node> BinaryTree;

2.5.2 多叉树

多叉树一般是用一个二维数组来维护,第一个坐标就是这个节点的data,第二维表示这个节点能直接到达的点

vector<int> Tree[N];
//给a节点插入一个b元素
void add(int a, int b) {
	Tree[a].push_back(b);
}

2.5.3 遍历

树有四种遍历方式,下面以二叉树为例

2.5.3.1 preorder

vector<Node> BinaryTree[N];

void preorder(int root) {
	if (root == -1) return;
	printf("%d", BinaryTree[root].data);
	preorder(BinaryTree[root].left);
	preorder(BinaryTree[root].right);
}

2.5.3.2 inorder

vector<Node> BinaryTree[N];

void inorder(int root) {
	 if (root == -1) return;
	 inorder(BinaryTree[root].left);
	 printf("%d", BinaryTree[root].data);
	 inorder(BinaryTree[root].right);
}

2.5.3.3 postorder

vector<Node> BinaryTree[N];

void postorder(int root) {
	if (root == -1) return;
	postorder(BinaryTree[root].left);
	postorder(BinaryTree[root].right);
	printf("%d", BinaryTree[root].data);
}

2.5.3.4 levelorder

vector<Node> BinaryTree[N];

void levelorder(int root) {
	queue<int> q;
	q.push(root);
	while (q.size()) {
		int size = q.size();
		for (int i = 0; i < size; i++) {
			int t = q.front();
			q.pop();
			printf("%d", t);
			if (BinaryTree[t].left != -1) q.push(BinaryTree[t].left);
			if (BinaryTree[t].right != -1) q.push(BinaryTree[t].right);
		}
	}
}

2.5.4 构造

2.5.4.1 通过先序遍历和中序遍历构造二叉树

int build(vector<int> preorder, vector<int> inorder) {
	if (preorder.size() <= 0 || inorder.size() <= 0) return -1;
	int root = preorder[0];
	int k = 0;
	while (k < inorder.size() && inorder[k] != root) k++;
	BinaryTree[root].data = root;
	BinaryTree[root].left = build(
		vector<int>(preorder.begin() + 1, preorder.begin() + k + 1),
		vector<int>(inorder.begin(), inorder.begin() + k)
	);
	BinaryTree[root].right = build(
		vector<int>(preorder.begin() + k + 1,preorder.end()),
		vector<int>(inorder.begin() + k + 1,inorder.end())
	);
	return root;
}

当然这种写法比较容易爆内存所以也可以使用指针的形式来写

int build(int pl, int pr, int il, int ir) {
	if (pl > pr || il > ir) return -1;
	int root = preorder[pl];
	int k = il;
	while (k <= ir && inorder[k] != root) k++;
	BinaryTree[root].data = root;
	BinaryTree[root].left = build(pl + 1, k - il + pl, il, k - 1);
	BinaryTree[root].right = build(pr - ir + k + 1, pr, k + 1, ir);
	return root;
}

2.5.4.2 通过后序遍历和中序遍历构造二叉树

int build(vector<int> postorder, vector<int> inorder) {
	if (postorder.size() <= 0 || inorder.size() <= 0) return -1;
	int root = postorder[postorder.size() - 1];
	int k = 0;
	while (k < inorder.size() && inorder[k] != root) k++;
	BinaryTree[root].data = root;
	BinaryTree[root].left = build(
		vector<int>(postorder.begin(),postorder.begin() + k),
		vector<int>(inorder.begin(), inorder.begin() + k)
	);
	BinaryTree[root].right = build(
		vector<int>(postorder.begin() + k, postorder.end() - 1),
		vector<int>(inorder.begin() + k + 1, inorder.end())
	);
	return root;
}

当然这种写法比较容易爆内存所以也可以使用指针的形式来写

int build(int pl, int pr, int il, int ir) {
	if (pl > pr || il > ir) return -1;
	int root = postorder[pr];
	int k = il;
	int k = il;
	while (k <= ir && inorder[k] != root) k++;
	BinaryTree[root].left = build(pl, k - il + pl - 1, ir, k - 1);
	BinaryTree[root].right = build(k - il + pl, pr - 1, k + 1, ir);
	return root;
}

2.5.4.3 通过前序遍历和后序遍历来判断是否能构造一棵二叉树

bool canbuild(vector<int> pre, vector<int> post) {
	if (pre.size() != post.size()) return false;
	if (pre.size() == 0 && post.size() == 0) return true;
	if (pre[0] != post[post.size() - 1]) return false;
	
	int root = pre[0];
	tree[root].data = root;
	if (pre.size() > 1) {
		int left = pre[1];
		int right = post[post.size() - 2];
		if (left != right) {
			int k1 = -1;
			for (int i = 0; i < pre.size(); i++) {
				if (pre[i] == right) {
					k1 = i;
					break;
				}
			}
			if (k1 == -1) return false;
			int k2 = -1;
			for (int i = 0; i < post.size(); i++) {
				if (post[i] == left) {
					k2 = i;
					break;
				}
			}
			if (k2 == -1) return false;
			return canbuild(
					vector<int> (pre.begin() + 1,pre.begin() + k1),
					vector<int> (post.begin(),post.begin() + k2 + 1)
				) && 
				canbuild(
					vector<int> (pre.begin() + k1,pre.end()),
					vector<int> (post.begin() + k2  +1,post.end() - 1)
				);
		}
		else {
			return canbuild(
				vector<int>(pre.begin() + 1, pre.end()),
				vector<int>(post.begin(), post.end() - 1)
			);
		}
	}
	return true;
}

2.5.5 AVL的插入操作

AVL是自平衡二叉搜索树,相较于ADT多了一个旋转自平衡的操作

明白这一点后其实就比较简单了所有函数和ADT完全相同只是添加一个自平衡函数balance()

原先insert返回t现在返回balance(t)删除同理

自平衡

那么AVL是怎么实现自平衡的呢其实很简单一共有四种情况

LL

LL 往左子树的左子树插入导致的不平衡 右旋:
第一步:将根节点的左孩子替换此节点 AvlNode k = t.left;
第二步:将 k节点的右孩子替换根节点的左孩子 t.left = k.right;
第三步:将根节点替换为 k节点的右孩子 k.right = t;

RR

RR 往右子树的右子树插入导致的不平衡 左旋:
第一步:将根节点的右孩子替换此节点 AvlNode k = t.right;
第二步:将 k节点的左孩子替换根节点的右孩子 t.right = k.left;
第三步:将根节点替换为 k节点的左孩子 k.left = t;

LR

LR 往左子树的右子树插入导致的不平衡 先对左子树左旋,再对根右旋

RL

RL 往右子树的左子树插入导致的不平衡 先对右子树右旋,再对根左旋
const int N = 30;
typedef struct node {
    int data;
    struct node* left;
    struct node* right;
}Node;

typedef Node* Tree;

int getHeight(Tree root) {
    if (root == NULL) return 0;
    return max(getHeight(root->left), getHeight(root->right)) + 1;
}

//右旋
Tree rotateWithRight(Tree root) {
    Tree t = root->left;
    root->left = t->right;
    t->right = root;
    return t;
}
//左旋
Tree rotateWithLeft(Tree root) {
    Tree t = root->right;
    root->right = t->left;
    t->left = root;
    return t;
}
//先堆左子树左旋,在对根右旋
Tree DoubleWithRight(Tree root) {
    root->left = rotateWithLeft(root->left);
    return rotateWithRight(root);
}

//先堆右子树右旋,在对根左旋
Tree DoubleWithLeft(Tree root) {
    root->right = rotateWithRight(root->right);
    return rotateWithLeft(root);
}

/**
* 分为四种情况
* LL 往左子树的左子树插入导致的不平衡 右旋
* RR 往右子树的右子树插入导致的不平衡 左旋
* LR 往左子树的右子树插入导致的不平衡 先对左子树左旋,再对根右旋
* RL 往右子树的左子树插入导致的不平衡 先对右子树右旋,再对根左旋
*/
Tree balance(Tree root) {
    if (root == NULL) return root;
    //左子树失衡
    if (getHeight(root->left) - getHeight(root->right) > 1) {
        //LL
        if (getHeight(root->left->left) > getHeight(root->left->right)) {
            root = rotateWithRight(root);
        }
        //LR
        else if (getHeight(root->left->left) < getHeight(root->left->right)) {
            root = DoubleWithRight(root);
        }
    }
    //右子树失衡
    else if (getHeight(root->left) - getHeight(root->right) < -1) {
        //RL
        if (getHeight(root->right->left) > getHeight(root->right->right)) {
            root = DoubleWithLeft(root);
        }
        //RR
        else if (getHeight(root->right->left) < getHeight(root->right->right)) {
            root = rotateWithLeft(root);
        }
    }
    return root;
}

Tree insert(Tree tree, int x) {
    if (tree == NULL) {
        Tree node = new Node();
        node->data = x;
        node->left = NULL;
        node->right = NULL;
        return node;
    }
    if (x < tree->data) {
        tree->left = insert(tree->left, x);
    }
    else {
        tree->right = insert(tree->right, x);
    }
    return balance(tree);
}

2.5.6 判断是否为红黑树

它具有以下 5 个属性:

  • 节点是红色或黑色。
  • 根节点是黑色。
  • 所有叶子都是黑色。(叶子是 NULL节点)
  • 每个红色节点的两个子节点都是黑色。
  • 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

分析:判断以下⼏几点:
1.根结点是否为⿊黑⾊色
2.如果⼀一个结点是红⾊色,它的孩⼦子节点是否都为⿊黑⾊色
3.从任意结点到叶⼦子结点的路路径中,⿊黑⾊色结点的个数是否相同

typedef struct node {
    int data;
    struct node* left;
    struct node* right;
}Node;

typedef node* Tree;

bool judge1(Tree root) {
    if (root == NULL) return true;
    if (root->data < 0) {
        if (root->left != NULL && root->left->data < 0) return false;
        if (root->right != NULL && root->right->data < 0) return false;
    }
    return judge1(root->left) && judge1(root->right);
}

int getBlackHeight(Tree root) {
    if (root == NULL) return 0;
    int count = root->data > 0 ? 1 : 0;
    return max(getBlackHeight(root->left), getBlackHeight(root->right)) + count;
}

bool judge2(Tree root) {
    if (root == NULL) return true;
    int left = getBlackHeight(root->left);
    int right = getBlackHeight(root->right);
    if (left != right)return false;
    return judge2(root->left) && judge2(root->right);
}

bool isRBT(Tree tree) {
    return tree->data < 0 || !judge1(tree) || !judge2(tree);
}

2.6 图

2.6.1 图的表示

图同样也是不能使用链表来表示否则会超时

  1. 稀疏图:邻接表 int g[N][N];
  2. 稠密图:邻接矩阵 vector<int> g[N];

2.6.2 dijkstra

既然图有两种表示方式自然dijkstra算法也有两种写法

2.6.2.1 经典dijkstra

int g[N][N];//邻接矩阵
int dist[N];//最短路
bool st[N];//标记是否被访问过
const int INF = INT_MAX;//最大值
int n;//点数
void dijkstra(int start) {
    //1 初始化
    for(int i = 1; i <= n; i++) {
        dist[i] = INF;
    }
    dist[start] = 0;
    
    //2 迭代每一个未被标记且路径最短的点
    for(int i = 1; i <= n; i++) {
        //初始化这个点
        int t = -1;
        //遍历所有点找到未被标记且路径最短的点
        for(int j = 1; j <= n; j++) {
            if(!st[j] && (t == -1 || dist[j] < dist[t])) {
                t = j;
            }
        }
        //将这个点标记
        st[t] = true;
        //用这个点更新其他点
        //具体就是 min(源->j,源->t + t->j)
        for(int j = 1; j <= n; j++) {
            dist[j] = min(dist[j],dist[t] + g[t][j]);
        }
    }
    
}

2.6.2.1 堆优化dijkstra

vector<Edge> g[N];
int dist[N];//最短路
bool st[N];//标记是否被访问过
const int INF = INT_MAX;//最大值
int n;//点数
void dijkstra(int start) {
	//定义一个小顶堆
	priority_queue<Edge, vector<Edge>, greater<Edge>> heap;
	//1. 初始化,并将第一个点的边放入堆
	for (int i = 1; i <= n; i++) {
		dist[i] = INF;
	}
	dist[start] = 0;
	heap.push({ start,0 });
	//遍历所有边
	while (heap.size()) {
		//拿出最小边
		auto min_edge = heap.top();
		heap.pop();
		if (st[min_edge.end]) continue;
		st[min_edge.end] = true;
		//更新这条最小边对应点的所有边
		for (int i = 0; i < g[min_edge.end].size(); i++) {
			auto edge = g[min_edge.end][i];
			//遍历这个点的所有边,更新路径
			//min(源->edge.end, 源->t.end + t.end->edge.wight)
			//将新的最小路径放入堆
			if (dist[min_edge.end] + edge.weight < dist[edge.end]) {
				dist[edge.end] = dist[min_edge.end] + edge.weight;
				heap.push({ edge.end,dist[edge.end] });
			}
		}
	}
}

2.6.3 遍历(Flood Fill)

Flood Fill:其实就是暴搜

2.6.3.1 dfs

int g[N][N];

int d[][2]{
	{1,0},
	{-1,0},
	{0,1},
	{0,-1}
};

int dfs(int x, int y) {
	if (g[x][y] == 0) return 0;
	g[x][y] = 0;
	int cnt = 1;
	for (int i = 0; i < 6; i++) {
		int a = x + d[i][0], b = y + d[i][1];
		if (a >= 0 && a < n && b >= 0 && b < n) {
			cnt += dfs(a, b);
		}
	}
	return cnt;
}

2.6.3.2 bfs

int g[N][N];

struct node {
	int x, y;
};

int d[][2]{
	{1,0},
	{-1,0},
	{0,1},
	{0,-1}
};

int bfs(int y, int x) {
	g[y][x] = 0;
	queue<node> q;
	q.push({ x,y });
	int cnt = 1;
	while (q.size()) {
		int size = q.size();
		for (int i = 0; i < size; i++) {
			node t = q.front();
			q.pop();
			for (int j = 0; j < 6; j++) {
				int a = t.x + d[j][0], b = t.y + d[j][1];
				if (a >= 0 && a < n && b >= 0 && b < n) {
					g[b][a] = 0;
					cnt++;
					q.push({ a,b });
				}
			}
		}
	}
	return cnt;
}

2.7 并查集

并查集主要有两个操作一个是合并两个集合另一个查看两个元素是否在一个集合,这两个操作都是基于find操作的

2.7.1 find

int p[N];

int find(int x) {
	if (x == p[x]) p[x] = find(p[x]);
	return p[x];
}

void merge(int a, int b) {
	int fa = find(a), fb = find(b);
	if (fa != fb) {
		p[fb] = find(fa);
	}
}

bool check(int a, int b) {
	return find(a) == find(b);
}

2.8 哈希

二次探测法

2.9对顶堆

典型例子是数据流中的中位数

    //大顶堆存左半边
    priority_queue<int,vector<int>,less<int>> left;
    //小顶堆存右半边
    priority_queue<int,vector<int>,greater<int>> right;
    /** initialize your data structure here. */
    MedianFinder() {
    }
    
    //如果left大小等于right那么插入一个元素到left
    //否则插入到right
    //插入规则是先插入到另一个堆,然后拿这个堆顶插入到这个堆
    void addNum(int num) {
        if(left.size() == right.size()) {
            right.push(num);
            left.push(right.top());
            right.pop();
        }
        else if(left.size() == right.size() + 1) {
            left.push(num);
            right.push(left.top());
            left.pop();
        }
    }
    
    //如果是奇数那么直接那左半边第一个数
    //如果是偶数则左右各取一个算平均值
    double findMedian() {
        if(left.size() > right.size()) return left.top();
        return 1.0 * (left.top() + right.top()) / 2;
    }

3 排序算法

3.1 插入排序

3.1.1 插入排序

//直接插入排序
void insert_sort(int arr[], int size) {
	for (int i = 1; i < size; i++) {
		int j = 0;
		int t = arr[i];
		for (j = i; j > 0; j--) {
			if (t < arr[j - 1]) {
				arr[j] = arr[j - 1];
			}
			else {
				break;
			}
		}
		arr[j] = t;
	}
}

//折半插入排序
void BInsertSort(int arr[], int size) {
	for (int i = 1; i < size; i++) {
		int t = arr[i];
		int left = 0;
		int right = i - 1;
		while (left <= right) {
			int mid = (left + right) / 2;
			if (t < arr[mid]) {
				right = mid - 1;
			}
			else{
				left = mid + 1;
			}
		}
		for (int j = i; j > left; j--) {
			arr[j] = arr[j - 1];
		}
		arr[left] = t;
	}
}

//希尔排序
void shellSort(int arr[], int size) {
	int gap = size / 2;//增量变量
	while (gap > 0) {
		for (int i = 0; i < gap; i++) {
			for (int j = i + gap; j < size; j += gap) {
				int t = arr[j];
				int k = 0;
				for (k = j; k >= 0; k--) {
					if (t < arr[k-1]) {
						arr[k] = arr[k - 1];
					} 
					else {
						break;
					}
				}
				arr[k] = t;
			}
		}
		gap /= 2;
	}
}

3.1.2 判断一个数列是否是插入排序的结果

例如给定初始序列,以及经过某种排序方法多次迭代后的序列,判断是否为插入排序

3 1 2 8 7 5 9 4 6 0
1 2 3 7 8 5 9 4 6 0

int p = 0;
while (p < n - 1 && v2[p] <= v2[p + 1]) {
	p++;
}
p++;
bool isInsert = true;
//如果前半段有序,后半段两个数组都相同,那么一定是插入排序
for (int i = p; i < n; i++) {
	if (v1[i] != v2[i]) {
		isInsert = false;
		break;
	}
}
//插入排序
if (isInsert) {
	cout << "Insertion Sort" << endl;
	for (int i = p; i >= 0; i--) {
		if (v2[i] >= v2[i - 1]) break;
		swap(v2[i], v2[i - 1]);
	}
}

3.2 归并排序

void merge(int* arr, int left, int mid, int right) {
    //开辟左数组
    vector<int> vleft(mid-left+1);
    //开辟右数组
    vector<int> vright(right-mid);
    //将原先数组的左半部分copy到左数组
    for(int i = left; i <= mid; i++) vleft[i-left] = arr[i];
    //将原先数组的右半部分copy到右数组
    for(int i = mid + 1; i <= right; i++) vright[i-mid-1] = arr[i];

    //排序
    int k = left,i = 0, j = 0;
    //把小的放进去
    while(i < mid-left+1 && j < right-mid) {
        if(vleft[i] < vright[j]) arr[k++] = vleft[i++];
        else arr[k++] = vright[j++];
    }

    //剩余的放进去
    while(i < mid-left+1) arr[k++] = vleft[i++];
    while(j < right-mid) arr[k++] = vright[j++];
}

void mergeSort(int* arr, int left, int right) {
    if (left < right) {
        int mid = (left + right) / 2;
        //左递归
        mergeSort(arr, left, mid);
        //右递归
        mergeSort(arr, mid + 1, right);
        //合并
        merge(arr, left, mid, right);
    }
}

3.3 堆排序

使用down递归写法

int heap[100010];
int Size = 0;
int n, m;
void down(int u) {
    int t = u;
    if (2 * u <= Size && heap[2 * u] < heap[t]) t = 2 * u;
    if (2 * u + 1 <= Size && heap[2 * u + 1] < heap[t]) t = 2 * u + 1;
    if (t != u) {
        swap(heap[u], heap[t]);
        down(t);
    }
}

void heap_sort() {
    for (int i = n / 2; i; i--) down(i);
}

使用迭代写法

   
//堆排序
void down() {
	int p = n - 1;
	while (p > 0 && heap[p] > heap[0]) {
		p--;
	}
	swap(heap[0], heap[p]);
	int child = 1;
	//left_child = i * 2 + 1
	//right_child = i * 2 + 2
	//每次比较左右孩子取较大的那个下滤也就是著名的down操作
	for (int i = 0; i * 2 + 1 <= p; i = child) {
		int left_child = i * 2 + 1, right_child = i * 2 + 2;
		//默认左孩子大
		if (left_child < p) child = left_child;
		//如果存在右孩子且比左孩子大那么更新为左孩子
		if (right_child < p && heap[right_child] > heap[left_child]) child = right_child;
		//下滤
		if (heap[child] > heap[i]) swap(heap[i], heap[child]);
		//左右孩子都比其小时说明排序成功break
		else break;

	}
}

void heap_sort() {
	for (int i = 0; i < n; i++) down();
}

3.4 快速排序

void quick_sort(int* q, int l, int r)
{
    if(l >= r) return;
    int i = l - 1;
    int j = r + 1;
    int x = q[l + (r-l)/2];
    while(i < j) {
        while(q[++i] < x);
        while(q[--j] > x);
        if(i < j) swap(q[i],q[j]);
    }
    quick_sort(q,l,j);
    quick_sort(q,j+1,r);
}

4 其他常见算法

4.1 二分

//求左边界板子
//【left,right】
//mid = (left+right)/2
//思考nums[mid]==target时,要求左边界所以将右边界左移right = mid-1;
//左边界返回left
int L(vector<int>& nums,int target) {
    int left = 0;
    int right = nums.size() - 1;
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] < target) {
            left = mid + 1;
        }
        else right = mid-1;
    }
    if(left >= nums.size()) return -1;
    return nums[left] == target ? left : -1;
}

//求右边界板子
//【left,right】
//mid = (left+right)/2
//思考nums[mid]==target时,要求右边界所以将左边界右移left = mid + 1;
//右边界返回right
int R(vector<int>& nums,int target) {
    int left = 0;
    int right = nums.size() - 1;
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] <= target) {
            left = mid + 1;
        }
        else right = mid-1;
    }
    if(right < 0) return -1;
    return nums[right] == target ? right : -1;        
}

4.2 前缀和

for(int i = 1; i <= n; i++) {
    nums[i] += nums[i-1];
}

4.3 差分

差分其实就是前缀和的逆过程

void insert(vector<int>& b,int l, int r, int c) {
    b[l] += c;
    b[r+1] -= c;
}

4.4 双指针

这个概念比较大,两个指针的我都叫他双指针

4.5 贪心

这个完全玄学,太难了

4.6 动规

这个PAT考的比较少,主要考察

  1. 背包问题
  2. 计数类DP(整数划分)
  3. 记忆化搜索

5 高精度

因为c++的数字类型长度有限制比如int只能到2^32-1故一些数据量较大的数无法表示所以有了高精度的算法

其实可以偷懒使用python

5.1 高精度加法

string add(string a, string b) {
    vector<int> A, B;
    cin >> a >> b;
    for (int i = a.size() - 1; i >= 0; i--) {
        A.push_back(a[i] - '0');
    }
    for (int i = b.size() - 1; i >= 0; i--) {
        B.push_back(b[i] - '0');
    }
    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size() || i <  B.size(); i++) {
        if (i < A.size()) t += A[i];
        if (i < B.size()) t += B[i];
        C.push_back(t % 10);
        t /= 10;
    }
    if (t != 0) C.push_back(t);
    string c = "";
    for (int i = C.size() - 1; i >= 0; i--) {
        c.append(C[i]);
    }
    return c;
}

5.2 高精度减法

vector<int> sub(vector<int>& A, vector<int>& B) {
	vector<int> C;
	int t = 0;
	for (int i = 0; i < A.size(); i++) {
		t += A[i];
		if (i < B.size()) t -= B[i];
		C.push_back((t + 10) % 10);
		if (t < 0) t = -1;
		else t = 0;
	}

	while (C.size() > 1 && C.back() == 0) C.pop_back();
	return C;
}

5.2 高精度乘法

vector<int> mul(vector<int>& A, int b) {
    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size() || t; i++) {
        if (i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

5.4 高精度除法

vector<int> div(vector<int>& A, int b, int& r) {
    vector<int> C(A.size());
    for (int i = 0; i < A.size(); i++) {
        r = r * 10 + A[i];
        C[i] = r / b;
        r %= b;
    }

    reverse(C.begin(), C.end());
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

6 进位制

考察任意进制间的转换,下面以十进制和二进制之间的转换为例

十进制->二进制:一直取余和除,直到0
二进制->十进制:每次先乘以进制然后加上最小位

typedef long long LL;

using namespace std;
//将r进制转为十进制
LL toTen(string s, int r) {

    LL res = 0;
    for (int i = 0; i < s.length(); i++) {
        res = res * r + mp[s[i]];
    }
    return res;
}

将十进制转换为r进制

    vector<int> nums;
    while (n) {
        nums.push_back(n % r);
        n /= r;
    }
    int res = 0;
    for (int i = 0; i < nums.size(); i++) {
        res = res* r + nums[i];
    }

7 数学常识

7.1 判断一个数是否为质数

bool isPrime(int r) {
    if (r <= 1) return false;
    for (int i = 2; i * i <= r; i++) {
        if (r % i == 0) return false;
    }
    return true;
}

i*i可能会超出int的范围所以一般写成下面的那种写法

bool isPrime(int r) {
    if (r <= 1) return false;
    for (int i = 2; i <= sqrt(r); i++) {
        if (r % i == 0) return false;
    }
    return true;
}

7.2 最大公约数

/*
求两个正整数 a 和 b 的 最大公约数 d
则有 gcd(a,b) = gcd(b,a%b)
证明:
    设a%b = a - k*b 其中k = a/b(向下取整)
    若d是(a,b)的公约数 则知 d|a 且 d|b 则易知 d|a-k*b 故d也是(b,a%b) 的公约数
    若d是(b,a%b)的公约数 则知 d|b 且 d|a-k*b 则 d|a-k*b+k*b = d|a 故而d|b 故而 d也是(a,b)的公约数
    因此(a,b)的公约数集合和(b,a%b)的公约数集合相同 所以他们的最大公约数也相同 证毕#
*/
int gcd(int a, int b){
    return b ? gcd(b,a%b):a; 
}

8 字符串处理

主要考察string类常见操作,以及编码能力(做好一到算法题写一两百行代码的心里准备)

9 模拟

主要考察英语阅读理解能力和编码能力(做好一到算法题写一两百行代码的心里准备),读懂题目按照题目规则编写代码即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/60157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机网络】计算机网络复习总结 ------ 物理层

计算机网络 内容管理物理层 physical layer相关概念术语信息数据 data信号 signal码元 code cell 【波特率B --- 信号v】比特率R ---- 数据v基带信号 baseband带通&#xff08;频带&#xff09;信号单工 simplex 半双工 全双工失真理想信道奈奎斯特定理 &#xff08;理想&#…

[附源码]Python计算机毕业设计Django求职招聘网站

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

室内温度控制仿真(Simulink+PLC)

本篇博客将会和大家一起一步步解读Simulink自带的仿真模型(Thermal Model of a House),之后再讨论PLC控制系统控制室内环境温度的一些经验方法。温度控制的大部分控制方法都是采用PID控制,有关PLC的PID控制相关内容可以参看专栏的其它文章,链接如下: 博途PLC 1200/1500P…

CN_计算机网络性能指标@信道利用率@信道吞吐率

文章目录性能指标带宽(Bandwidth)&#x1f388;时延(Dely)发送时延&#x1f388;传播时延处理时延排队时延时延带宽积往返时延(Round-Trip Time,RTT)吞吐量(Throughput)速率(Speed)带宽(Bandwidth)信道利用率补充利用率信道利用率发送周期发送时间(传输时间)信道利用率计算&…

(附源码)springboot《升学日》日本大学信息及院校推荐网站 毕业设计 251949

基于springboot《升学日》日本大学信息及院校推荐网站 摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于《升学日》日本大学信息及院校推荐网站当然也不能排除在外&#xff0c;随着网络…

面向对象中的继承

面向对象中的继承 封装 低耦合&#xff0c;高内聚 多态 重载&重写 重载 其实这是后台的知识&#xff0c;这么做的原因是&#xff1a;涉及到服务器的承压能力。减轻并发数 重写 子类重写父类中的方法 怎么理解面向对象&#xff1f; 一切皆对象……学院派的答法&#xff0c;尽…

Some App Tech Support 一些应用技术支持

Some App Tech Support 一些应用技术支持 Getting Support: mail: qiudi7323gmail.com or leave comment below. 获得支持&#xff1a; 邮件&#xff1a;qiudi7323gmail.com 或者在下面留下评论。

深入理解Nginx线程池【内附原理讲解以及源码分析】

文章目录&#x1f680;前言❓什么是并发编程⭐多进程和多线程并发编程的比较&#x1f34e;线程池⭐线程池组成⭐线程池的核心组件⭐源码分享⭐线程池关键结构体刨析⭐线程池关键函数刨析&#x1f330;总结&#x1f680;前言 因为前段时间项目需要所以阅读分析了Nginx线程池源码…

[每周一更]-(第23期):Docker 逻辑图及常用命令汇总

Docker是一种轻量级的虚拟化技术&#xff0c;同时是一个开源的应用容器运行环境搭建平台&#xff0c;可以让开发者以便捷方式打包应用到一个可移植的容器中&#xff0c;然后安装至任何运行Linux或Windows等系统的服务器上。相较于传统虚拟机&#xff0c;Docker容器提供轻量化的…

认识哈希表

作者&#xff1a;~小明学编程 文章专栏&#xff1a;Java数据结构 格言&#xff1a;目之所及皆为回忆&#xff0c;心之所想皆为过往 目录 为什么我们需要哈希表&#xff1f; 哈希表的原理 什么是哈希值 冲突 负载因子 解决冲突 闭散列 开散列/哈希桶 代码实现 不考虑…

我们为什么需要调用InitCommonControls?

很多第一次使用外壳通用控件 (Shell common controls) 的新手碰到的问题之一是&#xff1a;他们经常忘记调用 InitCommonControls 。 但是如果你有机会查看这个函数的反汇编代码&#xff0c;则你会发现&#xff1a;这个函数实际上不做任何事情&#xff0c;就像另外一个函数 Flu…

python--pip常用命令、国内PyPI镜像、使用pip安装第三方库

让我们来看看具体内容&#xff1a; 一. pip常用命令 列出已安装的包&#xff1a; pip freeze or pip list 导出requirements.txt&#xff1a; pip freeze ><目录>/requirements.txt 在线安装包&#xff08;模块库&#xff09;&#xff1a; pip install <包名>…

论文解读:为了数据中心的未来,存算这对CP竟然又离了

古语有云&#xff1a;天下大势分久必合、合久必分。 同样&#xff0c;在数据中心多年的发展历程中&#xff0c;计算与存储也经历了多次分分合合。从大型机的计算与存储紧耦合&#xff0c;到小型机经典的IOE存算分离架构&#xff0c;再到随云兴起的超融合让存算再次融合&#x…

达梦数据库的名词解释

达梦数据库的名词解释 C/S、客户端、服务器、物理结构、逻辑结构、文件系统、文件、数据库、数据库实例、表空间、表、段、簇、页、用户、模式、角色、 一、数据库的组成 客户端连接服务器&#xff0c;通过数据库实例&#xff08;共享内存后台进程及线程&#xff09;将磁盘内…

关于修复预制体上UnityEngine.UI引用丢失的一种思路

在开发项目过程中&#xff0c;如果出现了Unity版本变化&#xff0c;有可能会导致一些预制体上的UI组件丢失&#xff0c;特别是大量UI脚本&#xff0c;明明一看就知道这个是Text组件&#xff0c;但是一个大大的missing出现在预制体上&#xff0c;让人产生了莫名的恐慌。 一、根…

头歌计算机组成原理MIPS寄存器文件设计

全部答案点击底部 <?xml version"1.0" encoding"UTF-8" standalone"no"?> <project source"2.15.0.2.exe" version"1.0"> This file is intended to be loaded by Logisim http://logisim.altervista.org &…

Linux | 二级页表的虚拟地址是怎么转换的?

文章目录页的概念可执行文件的虚拟地址二级页表的转换二级页表的优点页的概念 在聊文件系统时&#xff0c;我提到操作系统是以块为基本单位进行IO的&#xff0c;一个块的大小为4KB&#xff0c;在文件系统中它的名字叫做块&#xff0c;在内存系统中它的名字叫做页&#xff0c;p…

并发编程十 定时任务定时线程池

一 ScheduledThreadPoolExecutor 定时线程池类的类结构图 它接收SchduledFutureTask类型的任务&#xff0c;是线程池调度任务的最小单位&#xff0c;有三种提交任务的方式&#xff1a; schedulescheduledAtFixedRatescheduledWithFixedDelay 它采用DelayQueue存储等待的任务…

带你玩转序列模型之Bleu得分注意力模型语音识别

目录 一.Bleu得分 二.注意力模型直观理解 三.注意力模型 四.语音识别 五.触发字检测 一.Bleu得分 先跳过&#xff0c;等回头用得到了再来补。 二.注意力模型直观理解 在本周大部分时间中&#xff0c;你都在使用这个编码解码的构架&#xff08;a Encoder-Decoder archit…

MATLAB算法实战应用案例精讲-【图像处理】目标检测

前言 目标检测,也叫目标提取,是一种基于目标几何和统计特征的图像分割。它将目标的分割和识别合二为一,其准确性和实时性是整个系统的一项重要能力。尤其是在复杂场景中,需要对多个目标进行实时处理时,目标自动提取和识别就显得特别重要。 随着计算机技术的发展和计算机视…