目录
1.冒泡排序
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int temp;
for(int i=0;i<nums.size()-1;i++)
{
bool flag = false;
for(int j=0;j<nums.size()-1-i;j++)
{
if(nums[j] > nums[j+1])
{
temp = nums[j+1];
nums[j+1] = nums[j];
nums[j] = temp;
flag = true;
}
}
if(!flag) break; 。// 循环无交换,就结束
}
return nums;
}
};
时间复杂度:n^2 稳定排序
2.选择排序
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int minIndex;
int n;
n = nums.size();
for(int i=0;i<n-1;i++)
{
minIndex = i;
for(int j=i+1;j<n;j++)
{
if(nums[minIndex] > nums[j])
{
minIndex = j;
}
}
swap(nums[i],nums[minIndex]);
}
return nums;
}
};
时间复杂度:n^2 不稳定排序
3.插入排序
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int key;
int n=nums.size();
// 默认第一个数字已经排序
for(int i=1;i<n;i++)
{
key = nums[i];
int j = i-1;
// 前面默认排序号,i-1(j)就一定是最大的
while(j>=0 && nums[j]>key)
{
nums[j+1] = nums[j];
j--;
}
// 将key值插入
nums[j+1] = key;
}
return nums;
}
};
时间复杂度:n^2 稳定排序
4.希尔排序
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
//希尔排序(高级插入排序)
int i, j, inc, n, key;
n = nums.size();
for (inc = n / 2; inc > 0; inc /= 2) {
for (i = inc; i < n; i++) {
j = i - inc;
key = nums[i];
while (j >= 0 && key < nums[j]) {
nums[j + inc] = nums[j];
j = j - inc;
}
nums[j + inc] = key;
}
}
return nums;
}
};
时间复杂度:O(n²)!~O(nlogn) 不稳定
5.快速排序
void quickSort(vector<int>&nums,int startIndex,int endIndex)
{
if(startIndex >= endIndex)
return;
int left,right,priot;
left = startIndex;
right = endIndex;
priot = nums[left];
while(right>left)
{
// 右侧寻找小于左侧,用作交换的值
while(nums[right]>=priot && right>left)
right--;
if(right>left)
{
nums[left] = nums[right];
}
while(nums[left]<=priot && right>left)
left++;
if(right>left)
{
nums[right] = nums[left];
}
}
nums[left] = priot;
quickSort(nums,startIndex,left-1);
quickSort(nums,left+1,endIndex);
}
时间复杂度nlogn,最坏n^2 空间复杂度logn
6.堆排序
- 建大顶堆,递归的方法( O(n) )
- 堆顶就是最大值,依次将堆顶与堆末尾的数字交换,然后截去最大的那个数
- 继续堆排序
时间复杂度:O( nlog(n) )
#include "../utils.h"
/**
* 维护堆的性质
* @param arr 存储堆的数组
* @param len 数组长度
* @param i 待维护节点的下标
*/
void heapify(int arr[], int len, int i)
{
int largest = i;
int lson = i * 2 + 1;
int rson = i * 2 + 2;
if (lson < len && arr[largest] < arr[lson])
largest = lson;
if (rson < len && arr[largest] < arr[rson])
largest = rson;
if (largest != i)
{
swap(&arr[largest], &arr[i]);
heapify(arr, len, largest);
}
}
// 堆排序入口
void heap_sort(int arr[], int len)
{
int i;
// 建堆
for (i = len / 2 - 1; i >= 0; i--)
heapify(arr, len, i);
// 排序
for (i = len - 1; i > 0; i--)
{
swap(&arr[i], &arr[0]);
heapify(arr, i, 0);
}
}
int main(int argc, char const *argv[])
{
test(&heap_sort);
return 0;
}
7.哈希表
原理:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DEFAULT_SIZE 16
typedef struct _ListNode{
struct _ListNode *next;
int key;
void *data;
}ListNode;
typedef ListNode *List;
typedef ListNode *Element;
typedef struct _HashTable{
int TableSize;
List *Thelists;
}HashTable;
// 哈希函数
int Hash(int key,int TableSize)
{
return (key%TableSize);
}
// 初始化HashTable
HashTable *InitHash(int TableSize)
{
int i=0;
HashTable *hTable = NULL;
if(TableSize<=0)
{
TableSize = DEFAULT_SIZE;
}
hTable = (HashTable*)malloc(sizeof(HashTable));
if(NULL == hTable)
{
printf("HashTable malloc error!\n");
return NULL;
}
hTable->TableSize = TableSize;
// 为Hash桶分配内存空间,其为一个指针数组
hTable->Thelists = (List*)malloc(sizeof(List)*TableSize);
if(NULL==hTable->Thelists)
{
printf("HashTable malloc error\n");
free(hTable);
return NULL;
}
// 为Hash桶对应的指针数组初始化链表节点
for(i=0;i<TableSize;i++)
{
hTable->Thelists[i] = (ListNode*)malloc(sizeof(ListNode));
if(NULL == hTable->Thelists[i])
{
printf("HashTable malloc error\n");
free(hTable->Thelists);
free(hTable);
return NULL;
}
else
{
memset(hTable->Thelists[i],0,sizeof(ListNode));
}
}
return hTable;
}
// 从哈希表中根据键值查找元素
Element Find(HashTable *HashTable ,int key)
{
int i=0;
List L= NULL;
Element e = NULL;
i = Hash(key,HashTable->TableSize);
L = HashTable->Thelists[i];
e = L->next;
while(e != NULL && e->key != key)
{
e = e->next;
}
return e;
}
void Insert(HashTable *HashTable,int key,void *value)
{
Element e= NULL;
Element tmp = NULL;
List L=NULL;
e = Find(HashTable,key);
if(NULL == e)
{
tmp = (Element)malloc(sizeof(ListNode));
if(NULL==tmp)
{
printf("mlloc error");
return ;
}
L = HashTable->Thelists[Hash(key,HashTable->TableSize)];
tmp->data = value;
tmp->key = key;
tmp->next = L->next;
L->next = tmp;
}
else{
printf("the key already exist\n");
}
}
// 哈希表中提取元素
void *Retrieve(Element e)
{
return e?e->data:NULL;
}
// 哈希表删除元素
void Delete(HashTable *HashTable,int key)
{
Element e=NULL;
Element last = NULL;
List L= NULL;
int i = Hash(key,HashTable->TableSize);
L = HashTable->Thelists[i];
last = L;
e = L->next;
while(e!=NULL && e->key!=key)
{
last = e;
e = e->next;
}
// 如果键值对存在
if(e)
{
last->next=e->next;
delete(e);
}
}
int main()
{
char *elems[] = {"翠花","小芳","苍老师"};
int i = 0;
HashTable *HashTable;
HashTable = InitHash(31);
Insert(HashTable,1,elems[0]);
Insert(HashTable,2,elems[1]);
Insert(HashTable,3,elems[2]);
Delete(HashTable,2);
for(i=0;i<4;i++)
{
Element e = Find(HashTable,i);
if(e){
printf("%s\n",(const char*)Retrieve(e));
}
else
{
printf("Not found [key:%d]\n",i);
}
}
system("pause");
return 0;
}
8.八皇后问题
- 设置attack存储 0,1表示 是否可施放置,设置queen表示图哪里放置了皇后,solve存储字符结果
- 回溯法:第一行第一个放置皇后,将行 列 斜线设置为不可放置,去第二行放置皇后,直到到达最后一行,若是不能到达最后一行放置则回溯
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void put_queen(int x,int y,vector<vector<int> >& attack)
{
// printf("11\n");
// 八个方向
static const int dx[] = {-1,1,0,0,-1,-1,1,1};
static const int dy[] = {0,0,-1,1,-1,1,-1,1};
// 皇后位置
attack[x][y] = 1;
// 将皇后 (行 列 斜线)全部设置为1
// 棋盘宽度 ,当i=0时,就是原位置,前面已经放置皇后了
for(int i=1;i<attack.size();i++)
{
// 八个方向
for(int j=0;j<8;j++)
{
int nx = x + i*dx[j];
int ny = y + i*dy[j];
// 这里一定要注意是 < 不是 <=
if(nx>=0 && ny>=0 && nx<attack.size() && ny<attack.size())
{
attack[nx][ny] = 1;
}
}
}
}
// 回溯法求n皇后问题
// k表示当前处理的行
// n表示n皇后问题
// queen存储皇后的位置
// attack标记皇后的攻击位置
// solve存储n皇后全部解法
void backtrack(int k,int n,vector<string>& queen,
vector<vector<int> >& attack,
vector<vector<string> >& solve)
{
// printf("22\n");
// 找到一组解
if(k==n)
{
solve.push_back(queen);
return;
}
// 遍历 0~n-1列,k行所有尝试
for(int i=0;i<n;i++)
{
if(attack[k][i] == 0)
{
vector<vector<int> > temp = attack;
queen[k][i] = 'Q';
put_queen(k,i,attack);
backtrack(k+1,n,queen,attack,solve);// 试探n+1行
attack = temp; // 恢复为之前的状态
queen[k][i] = '.';
}
}
}
vector<vector<string> > solveNQueens(int n)
{
// printf("33\n");
vector<vector<string> > solve;
vector<vector<int> > attack;
vector<string> queen;
for(int i=0;i<n;i++)
{
attack.push_back((vector<int>() ));
for(int j=0;j<n;j++)
{
attack[i].push_back(0);
}
queen.push_back("");
queen[i].append(n,'.');
}
backtrack(0,n,queen,attack,solve);
return solve;
}
int main()
{
vector<vector<string> > result;
result = solveNQueens(8);
printf("8皇后共有%d种解法:\n\n",result.size());
for(int i=0;i<result.size();i++)
{
printf("解法%d:\n",i+1);
for(int j=0;j<result[i].size();j++)
{
printf("%s\n",result[i][j].c_str());
}
printf("\n");
}
return 0;
}
9.满二叉树
排序二叉树(二分搜索树) 查找效率:O(h)
二叉搜索树实现方法(中序遍历可以得到递增的排序序列)
二叉搜索树插入
二叉搜索树删除
找左子树最大或右子树最小的代替(右子树中序遍历第一个)
遍历确定唯一二叉树
给定 先序+中序 or 后序+中序 可以唯一确定一颗二叉树
中序+后序确定二叉树:后序遍历的最后一个节点为root, 中序遍历中的root将二叉树划分为左、右子树,对于左右子树同样处理:递归
前序+中序确定二叉树:前序遍历的第一个节点为root, 中序遍历中的root将二叉树划分为左、右子树,对于左右子树同样处理:递归
而 先序+后序 不能
A
/
B
前序遍历: AB, 后序遍历: BA
A
\
B
前序遍历: AB, 后序遍历: BA
9.递归与非递归优缺点
递归的好处:代码相对非递归来说,更为简洁,并且思路清晰。
递归的坏处:由于递归需要系统堆栈,所以消耗空间要比非递归大很多。并且,如果递归深度太深,可能会导致系统奔溃。
非递归的优点:效率高,执行时间只会因为循环的次数增加而增加,没什么额外开销,也不会占用额外空间。
非递归的缺点:并不太容易理解,编写复杂问题时比较困难。
通俗地说,递归就是将问题层层细化、分解为多个小问题,然后一一解决。而非递归大多数情况下都编程者要去思考整个问题的解决办法,对编程者自身思维有很大挑战。
11.自上而下与自下而上算法
12.动态规划
动态规划:通过组合子问题的解来求解原问。不同于分治算法的互不相交,反复求解子问题,动态规划子问题可以重叠,且只求解一次,通常采用自底向上的方法。
- 确定dp[]含义
- 确定动态规划地推公式
- 确定初始化值
- 确定遍历顺序
- 打印数组
斐波那契数
class Solution {
public:
int fib(int n) {
if(n<2)
return n;
int MOD = 1000000007;
int p = 0, q = 1,r;
for(int i=2;i<=n;i++)
{
r = (p+q)%MOD;
p = q;
q = r;
}
return r;
}
};
爬楼梯
class Solution {
public:
int climbStairs(int n) {
if(n<=2)
return n;
int p=1,q=2,r;
for(int i=2;i<n;i++)
{
r = p+q;
p = q;
q = r;
}
return r;
}
};
13.栈与队列
1.栈的基本应用
- 逆波兰式(后缀表达式)
- 栈在递归中的应用,
- 迷宫小车(寻路),
- 搜索引擎
2.队列的基本应用
- 树的层次遍历
- 图的广度优先遍历
- 队列在操作系统中的调度算法,如先来先服务。
- 搜索引擎,爬虫
二叉树先序遍历迭代法
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if (root == nullptr) {
return res;
}
stack<TreeNode*> stk;
TreeNode* node = root;
while (!stk.empty() || node != nullptr) {
while (node != nullptr) {
res.emplace_back(node->val);
stk.emplace(node);
node = node->left;
}
node = stk.top();
stk.pop();
node = node->right;
}
return res;
}
};
用栈模拟队列
class MyQueue {
private:
stack<int> inStack,outStack;
void infoStack(){
while(!inStack.empty())
{
outStack.push(inStack.top());
inStack.pop();
}
}
public:
MyQueue() {
}
void push(int x) {
inStack.push(x);
}
int pop() {
if(outStack.empty())
{
infoStack();
}
int x;
x = outStack.top();
outStack.pop();
return x;
}
int peek() {
if(outStack.empty())
{
infoStack();
}
int x = outStack.top();
return x;
}
bool empty() {
if(inStack.empty() && outStack.empty())
return true;
else
return false;
}
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
*/
层序遍历(队列)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
// 定义一个二位数组
vector<vector<int>> ret;
// 当root为空指针时
if(!root)
return ret;
queue<TreeNode*> q;
q.push(root);
while(!q.empty())
{
int currentSize = q.size();
vector<int> res;
while(currentSize--)
{
auto node = q.front();
q.pop();
res.push_back(node->val);
if(node->left)
q.push(node->left);
if(node->right)
q.push(node->right);
}
ret.push_back(res);
}
return ret;
}
};
14.数据结构
数据结构(英语:data structure)是计算机中存储、组织数据的方式。
数据结构是一种具有一定逻辑关系,在计算机中应用某种存储结构,并且封装了相应操作的数据元素集合。它包含三方面的内容,逻辑关系、存储关系及操作。
不同种类的数据结构适合于不同种类的应用,而部分甚至专门用于特定的作业任务。例如,计算机网络依赖于路由表运作,B 树高度适用于数据库的封装。
15.广度优先遍历有深度优先遍历
深度优先遍历
主要思路是从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底…,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。
应用:
小车遍历迷宫
二叉树的遍历
广度优先遍历
广度优先遍历,指的是从图的一个未遍历的节点出发,先遍历这个节点的相邻节点,再依次遍历每个相邻节点的相邻节点。
小车遍历迷宫
树的遍历
层序遍历
最短路径
![在这里插入图片描述](https://img-blog.csdnimg.cn/488ac822b37344829ac13f3eb32f052e.png
16.哈夫曼树
应用:哈夫曼编码