重学数据结构与算法

学习数据结构与算法的目的:
优化时间复杂度与空间复杂度 优化时间复杂度与空间复杂度 优化时间复杂度与空间复杂度
教程总纲: 暴力解法(模拟)算法优化(递归/二分/排序/DP)时刻转换(数据结构)
在这里插入图片描述
在这里插入图片描述

1.时间复杂度的核心方法论

空间是廉价的,时间是昂贵的

相较于空间复杂度(投入金钱 增加算力),时间复杂度(消耗时间)更为重要!

在这里插入图片描述

降低时间与空间复杂度的方法:

在这里插入图片描述
时刻转换:选用合适的数据结构,进一步降低时间复杂度

例.输入数组 a = [1,2,3,4,5,5,6] 中查找出现次数最多的数值。

暴力解法是:两层for遍历,维护一个最大次数time_max,对每个元素计算出现次数time_tmp,与time_max进行对比,时间复杂度是 0 ( n 2 ) 0(n^2) 0(n2)

int main(){
	vector<int> a={1,2,3,4,5,5,6};
	int val_max=-1,time_max=0,time_tmp=0;
	for(int i=0;i<a.size();i++){
		time_tmp=0;
		for(int j=0;j<a.size();j++)
			if(a[i]==a[j]) time_tmp++;
		if(time_tmp>time_max){
			time_max=time_tmp;
			val_max=a[i];
		}
	}
	cout<<val_max<<" "<<time_max<<endl;
	return 0;
}

在这里插入图片描述

优化思想:如何仅用单层for循环完成,用hash思想,引入k-v字典数据结构map,一次for保存每个元素出现的次数,再求每个元素次数的最大值,时间复杂度是 0 ( 2 n ) 0(2n) 0(2n)

int main(){
	vector<int> a={1,2,3,4,5,5,6};
    map<int ,int> num_cnt;
    int val_max,time_max=0;
	for(int i=0;i<a.size();i++){
		num_cnt[a[i]]++; //counting the number of times a[i occurs in the vector a.
	}
    for(auto it:num_cnt){ //iterating over the map and printing the max time a[i] occurs for each element.  
        if(time_max < it.second){
            val_max=it.first; //assigning the maximum value from the map to val_max.
            time_max=it.second; //assigning the maximum count from the map to time_max.
        }
    }
	cout<<val_max<<" "<<time_max<<endl;
	return 0;
}

2.增删查——选取数据结构的基本方法

当你不知道用什么数据结构的时候:
分析需要对数据进行了哪些操作,根据数据操作,选取合适的数据结构 分析需要对数据进行了哪些操作,根据数据操作,选取合适的数据结构 分析需要对数据进行了哪些操作,根据数据操作,选取合适的数据结构
在这里插入图片描述

还用上面的例子介绍:

对于统计次数最多的元素,我们需要对数据结构进行以下操作:
在这里插入图片描述
具体的:
在这里插入图片描述
所以
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.线性表——如何完成基本增删查

实际上,有线性存储(数组)和链式存储(链表)两种结构,这里仅介绍链式存储。

在这里插入图片描述
单向链表:
在这里插入图片描述
循环链表:
在这里插入图片描述
双向链表:
在这里插入图片描述
双向循环链表:
在这里插入图片描述

线性表增删查:其他链表的操作与单向链表雷同,仅介绍单向链表

增加操作在这里插入图片描述
删除操作
在这里插入图片描述
查找操作:
在这里插入图片描述
在这里插入图片描述
总结:
链表的查找速度慢 ( 无法用 i n d e x ) O ( n ) ,但插入和删除 ( 改变指针 ) 方便 O ( 1 ) 链表的查找速度慢(无法用index)O(n),但插入和删除(改变指针)方便O(1) 链表的查找速度慢(无法用index)O(n),但插入和删除(改变指针)方便O(1)

在这里插入图片描述
在这里插入图片描述

链表的问题常常围绕数据顺序的处理链表反转快慢指针

例1.

在这里插入图片描述
为此,我们使用3个指针prev、curr、next,分别指向 新链表头节点、旧链表转换节点、旧链表转换节点的下一个,完成旧链表向链表逐个节点的转换。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

struct node{
    int data=0;
    node* next;
};

int main(){
    node*head=new node,*n1=new node,*n2=new node,*n3=new node;
    head->data=0;
    head->next=n1; n1->data=1; n1->next=n2; n2->data=2; n2->next=n3; n3->data=3;n3->next=NULL;
    node*tmp=head;
    //输出原链表
    while(tmp!=NULL){
        cout<<tmp->data<<" ";
        tmp=tmp->next;
    }cout<<endl;

    node* curr=head,*prev=head,*next=head->next;
    head->next=NULL;
    while(next!=NULL){
        curr=next; next=next->next;
        curr->next=prev; prev=curr;
    }
    //输出逆序链表
    while(curr!=NULL){
        cout<<curr->data<<" ";
        curr=curr->next;
    }
	return 0;
}

/*
0 1 2 3 
3 2 1 0
*/

例2.
在这里插入图片描述
在这里插入图片描述
slow走1步,fast走两步。(因为fast一次走两步,所以要防止fast到fast.next.next为空,所以while的判断条件是3个)
fast到达终点时,slow到达中点。

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

typedef struct node{
    int data=0;
    node* next;
}*Node;

int main(){
    node*head=new node,*n1=new node,*n2=new node,*n3=new node,*n4=new node,*n5=new node,*n6=new node;
    head->data=0;
    head->next=n1; n1->data=1; n1->next=n2; n2->data=2;
    n2->next=n3; n3->data=3;n3->next=n4,n4->data=4;n4->next=n5;
    n5->data=5;n5->next=n6;n6->data=6;n6->next=NULL;//1->2->3->4->5->6
    //快慢指针,求链表中间值
    node *fast=head,*slow=head;
    while(fast!=NULL&&fast->next!=NULL&&fast->next->next!=NULL){
        fast=fast->next->next;
        slow=slow->next;
    }
    cout<<slow->data<<endl;
	return 0;
}

例3.
在这里插入图片描述
基本思想是利用两个指针,一个快指针和一个慢指针,分别从链表头部开始遍历,快指针每次走两步,慢指针每次走一步,若快指针追上了慢指针,则说明链表存在环路;否则,当快指针到达链表尾部时,结束遍历,slow永远不可能和fast相等,链表不存在环路。

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

typedef struct node{
    int data=0;
    node* next;
}*Node;

bool cicle(node* head){
    node *fast=head,*slow=head;
    while (fast && fast->next) {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow) 
            return true;
    }
    return false;
}

int main(){
    node*head=new node,*n1=new node,*n2=new node,*n3=new node,*n4=new node,*n5=new node,*n6=new node;
    head->data=0;
    head->next=n1; n1->data=1; n1->next=n2; n2->data=2;
    n2->next=n3; n3->data=3;n3->next=n4,n4->data=4;n4->next=n5;
    n5->data=5;n5->next=n6;n6->data=6;n6->next=n2;//1->2->3->4->5->6
    //判断链表循环
    if(cicle(head)) cout<<"Loop found"; else cout<<"No loop found"; cout<<endl;
}

4.栈——先进后出的增删查

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
顺序栈:
推荐:用vector模拟栈时,仅允许在线性表尾部(栈顶)插入删除数据push_back()pop_back()

不推荐:也可以用数组模拟。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

链栈: 不需要头指针,进维护一个栈顶top指针。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
例1.
在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

int main(){
    string str;
    cin>>str;
    vector<char> stk;
    rep(i,0,str.size()){
        if(str[i]=='['||str[i]=='('||str[i]=='{') stk.push_back(str[i]);
        else if(str[i]==']' && stk.back()=='[') stk.pop_back();
        else if(str[i]==')' && stk.back()=='(') stk.pop_back();
        else if(str[i]=='}' && stk.back()=='{') stk.pop_back();
        else {cout<<" error!";break;}
    }
}

例2.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

int main(){
    vector<int> forword_stk,back_stk;
    //back_stk.back()是正在浏览的页面
    int n; cin>>n; int tmp;
    //保存顺序浏览过的页面page 1-5
    rep(i,1,6) back_stk.push_back(i);
    //回退到页面n
    while(back_stk.back()!=n){
        tmp=back_stk.back();
        forword_stk.push_back(tmp); 
        back_stk.pop_back(); 
    }
    cout<<"looking page "<<back_stk.back();
}

总结:
在这里插入图片描述

5.队列——先进先出的增删查

在这里插入图片描述
在这里插入图片描述

头指针front,尾指针rear

在这里插入图片描述

链队: 头节点仅用来表示队列(data=number),不用了存储数据
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
头节点的意义:给空链表的front 和rear指针一个指向,防止变成野指针。
在这里插入图片描述

顺序队列: 数组模拟,队尾插入时间复杂度为O(1),队头删除,后面的所有元素前移,时间复杂度为O(n),如果仅通过移动fornt指针的方式,会造成假溢出,空间不足的情况。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
实际上,上述两种解决方法都不好,假溢出最优的解决办法是构造循环队列

循环队列:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

例1.
在这里插入图片描述
在这里插入图片描述


总结:
在这里插入图片描述
在这里插入图片描述

6.数组——基于索引的查找

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
增加:
在这里插入图片描述
删除:
在这里插入图片描述
查找;
在这里插入图片描述
在这里插入图片描述
总结:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

7.字符串——字符串匹配与操作

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6Llh8zc-1686663211580)(null)]
插入:
在这里插入图片描述
删除:
在这里插入图片描述
字符串匹配:
在这里插入图片描述

暴力匹配:
在这里插入图片描述
在这里插入图片描述
例题. 可以暴力也,可以动态规划
在这里插入图片描述

暴力法:
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

int main(){
    string a="123456";
    string b="13452439";
    int max_len=0;
    string max_string;
    rep(i,0,a.size()){
        rep(j,0,b.size()){
            //先找到第一个匹配的字符,判断后续字符是否匹配
            if(a[i]==b[j]){
                for(int m=i,n=j;m<a.size() && n<b.size();m++,n++){ //m and n are indices in a and b, respectively, so we add 1 to
                    if(a[m]!=b[n]) break; //to stop at the first mismatch, which is at a[m]!=b[n] (which is true if m>n)
                    if(max_len<m-i){
                        max_len=m-i;//update the maximum length so far found, which is the length of the substring starting from a[i] to the
                        max_string=a.substr(i,max_len);//last character of a.  Note that i is not incremented.  This
                    }
                }
            }
        }
    }
    cout<<max_string<<endl;//print the substring found, which is the substring starting from a[i] to the last character of a
}

例题.
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

int main(){
    string a="you are a good man";
    vector<string> stk;
    string tmp;
    rep(i,0,a.size()){
        if(a[i]==' '){stk.push_back(tmp);tmp.clear();}
        else tmp+=a[i];
    } stk.push_back(tmp);
    while(!stk.empty()){cout<<stk.back()<<' ';stk.pop_back();}
}

总结:面试笔试常考字符串匹配!暴力->KMP
在这里插入图片描述

8.树&二叉树——分支与层次关系

在这里插入图片描述
在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fJUlteq1-1686663187757)(null)]
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
非完全二叉树,使用顺序存储会浪费大量的存储空间!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
递归实现二叉树的前、中、后序遍历

class node{ public: string val; node* left; node* right;};
void PreOrder(node* NODE){
    if(NODE==NULL)return;
    cout<<NODE->val<<" ";
    PreOrder(NODE->left);
    PreOrder(NODE->right);    
}
void InOrder(node* NODE){
    if(NODE==NULL)return;
    InOrder(NODE->left);
    cout<<NODE->val<<" ";
    InOrder(NODE->right);
}
void PostOrder(node* NODE){
    if(NODE==NULL)return;
    PostOrder(NODE->left);
    PostOrder(NODE->right);
    cout<<NODE->val<<" ";
}

在这里插入图片描述

二叉查找树(二次排序树): 左小右大,左子树上所有结点的关键字均小于根结点的关键字;右子树上的所有结点的关键字均大于根结点的关键字。中序遍历是有序数列!
在这里插入图片描述
在这里插入图片描述
利用二次排序树的性质,可以实现二分查找,加快查找速度!!!
在这里插入图片描述
二叉排序树的插入:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
二叉排序树删除:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

例题.
在这里插入图片描述
可以暴力搜索,也可以用字典树,层次遍历到叶子节点的路径。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
例题.层次遍历,维护OPEN表(队列),不断扩展队首子节点,加入队列。
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)
#include <iostream>
#include <queue>
using namespace std;

// 定义二叉树结构体
struct TreeNode { int val=-1; TreeNode* left; TreeNode* right; TreeNode(int x): val(x), left(NULL), right(NULL) {}};

// 前序建立二叉树,放回根节点,如输入:1 2 3 -1 -1 -1 4 -1 -1
TreeNode* buildTree() {
    int n;
    cin >> n;
    // 判断输入是否合法,空节点输入-1
    if (n == -1) return NULL;
    // 创建新节点
    TreeNode* root = new TreeNode(n);
    // 递归创建左右子树
    root->left = buildTree();
    root->right = buildTree();
    return root;
}

// 层次遍历输出二叉树,1 2 4 3
void levelOrder(TreeNode* root) {
    if (root == NULL) return;
    // 使用队列进行层次遍历
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
        TreeNode* cur = q.front();
        q.pop();//不断取出队首节点进行子节点扩展,扩展出的新节点放入队列(OPEN表)
        cout << cur->val << " ";
        if (cur->left != NULL) {
            q.push(cur->left);
        }
        if (cur->right != NULL) {
            q.push(cur->right);
        }
    }
}
//前序遍历,1 2 3 4
void preOrder(TreeNode* root) { 	
    if (root == NULL) return; 	
    cout << root->val << " ";
    preOrder(root->left); 	
    preOrder(root->right); 
}

int main() {
    TreeNode* root = buildTree();
    levelOrder(root);cout<<endl;
    preOrder(root);cout<<endl;
    return 0;
}

在这里插入图片描述

总结:
在这里插入图片描述

9.哈希表——高效查找的利器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

哈希冲突: 不同对象的哈希地址相同(键值对的值相同)

在这里插入图片描述

哈希函数设计: 下面几个方法都可能出现哈希冲突
在这里插入图片描述
在这里插入图片描述
冲突解决:

线性探测法(沿占用地址逐个向下遍历寻找未占用的地址存放)
在这里插入图片描述
在这里插入图片描述

链地址法(将相同哈希地址的记录存放在同一条链表上)
在这里插入图片描述
总结:优点(CUDR飞快)、缺点(处理输入顺序敏感的问题时,会破坏序列的构造顺序)
在这里插入图片描述

在这里插入图片描述
如C++的map
在这里插入图片描述
例子.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
例题.
在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)
#include <iostream>
#include <queue>
using namespace std;

int main() {
    map<string,int> database;
    string s;
    while(1){cin>>s;
        if(s=="-1") break;
        else {database[s]++;cout<<s<<" occurs "<<database[s]<<" times.\n";}
    }
    return 0;
}

总结:
在这里插入图片描述

10.递归——解决汉诺塔问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
例子.中序遍历
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
例子.汉诺塔问题
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总结:
在这里插入图片描述

11.分治——利用分治快速完成数据查找

在这里插入图片描述
在这里插入图片描述

分治需要使用递归
每轮递归的包括:分解问题、解决问题、合并结果

在这里插入图片描述

例子.
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以递归实现,也可以循环实现

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

void binaryfind(int e,vector<int> arr,int low,int hight){
    if(arr[(low+hight)/2] == e){cout<<"find "<<e<<endl;return;}
    else if(arr[(low+hight)/2] > e) return binaryfind(e,arr,low,(low+hight)/2-1);//left half sorted
    else return binaryfind(e,arr,(low+hight)/2+1,hight);//right half sorted
}

int main() {
    vector<int> v={1,2,3,5,7,9,12};
    binaryfind(2,v,0,v.size()-1);//1 2 3 5 7 9 12
    return 0;
}

例题.
在这里插入图片描述

在这里插入图片描述

总结:
在这里插入图片描述
在这里插入图片描述

12.排序——经典排序算法解析

在这里插入图片描述
在这里插入图片描述
冒泡排序: 相邻元素两两比较,逆序对交换,每轮将1个大的元素交换到最后,经过多轮迭代完成排序。稳定:元素相等时不做交换
在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

void bubble_sort(vector<int> &v){
    rep(i,1,v.size()){
        rep(j,0,v.size()-i){
            if(v[j] > v[j+1]) 
            	swap(v[j],v[j+1]);
        }
    }
}

插入排序: 维护一个排好序的序列,不断为每个未插入的元素,与序列元素比较,找到合适的插入位置。稳定排序。

在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

void insert_sort(vector<int> &v){
    int tmp;
    rep(i,1,v.size()){
        tmp=v[i];//取待排序元素v[i]
        int j=i-1;
        for(j=i-1;j>-1;j--){//在有序序列中,从后向前与tmp元素比较大小
            if(v[j]>tmp) v[j+1]=v[j];//将所有大于tmp的元素后移一位,保持有序序列
            else break;//如果tmp元素小于或等于元素j,则结束该次比较
        }
        v[j+1]=tmp;//将tmp元素放在合适的位置
    }
}

在这里插入图片描述

归并排序:分治+合并)将待排序序列从中点位置不断地二分为左右两个子序列,分别递归调用归并排序函数,直到每个子序列长度为1。再对两个已经有序的相邻子序列进行合并,得到一个新的有序序列。 稳定排序在这里插入图片描述
分治:当数组长度为 1 时,该数组就已经是有序的,不用再分解。

当数组长度大于 1 时,该数组很可能不是有序的。此时将该数组分为两段,再分别检查两个数组是否有序(用第 1 条)。如果有序,则将它们合并为一个有序数组;否则对不有序的数组重复第 2 条,再合并。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
合并: 归并排序最核心的部分是合并(merge)过程:将两个有序的数组 a[i] 和 b[j] 合并为一个有序数组 c[k]。

从左往右枚举 a[i] 和 b[j],找出最小的值并放入数组 c[k];重复上述过程直到 a[i] 和 b[j] 有一个为空时,将另一个数组剩下的元素放入 c[k]。

为保证排序的稳定性,前段首元素小于或等于后段首元素时(a[i] <= b[j])而非小于时(a[i] < b[j])就要作为最小值放入 c[k]。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

void customDoubleMerge(vector<int>&v,int left,int mid,int right){
    vector<int> tmp;//创建一个临时数组来存放合并后的元素
    int p1=left,p2=mid+1,k=left;//p1指向左界和右界开始位置的元素序列,k指向合并到的元素
    //合并[left,mid]和[mid+1,right]两个子序列到tmp中: 不断取 两个有序序列中最小的元素v[p1]或v[p2] 插入tmp中!!!直至其中一个序列耗尽
    while(p1<=mid && p2<=right){
        if(v[p1]<=v[p2]) tmp.push_back(v[p1++]);//如果左界元素小于右界元素,将它们放进tmp中
        else tmp.push_back(v[p2++]);//如果左界元素大于右界元素,将右界元素
    }
    // 此时存在一个序列未耗尽,p1=mid 或 p2=right
    while(p1<=mid)  tmp.push_back(v[p1++]);//将剩下的左界元素放进tmp中
    while(p2<=right) tmp.push_back(v[p2++]);//将剩下的右界元素放进tmp

    rep(i,0,tmp.size()) v[i+k]=tmp[i];//将tmp中的元素放回v原来的位置(注意偏移量k)
}
// 需要传入序列,及左右index
void customMergeSort(vector<int> &v, int left, int right){
    if(left<right){
        //二分为子序列
        int mid=(left+right)/2;
        //分别递归对左右进行排序
        customMergeSort(v, left, mid);
        customMergeSort(v, mid+1, right);
        //合并相邻子序列
        customDoubleMerge(v, left, mid, right);
    }
}

int main() {
    vector<int> v={2,9,2,4,6,8,1};
    customMergeSort(v,0,v.size()-1); //v=[2,41,2,4,6,8,1]->[2,4,2,41
    for(auto e:v)cout<<e;
    return 0;
}

在这里插入图片描述

快速排序: 也是不断的将序列不断二分,但分割的过程中,保证做左子序列元素<左子序列首元素<右子序列元素。具体通过维护两个指针实现,从左右端点不断向中间走,如果左指针小于首元素(左序列第一个元素),暂存左指针去看右指针,如果右指针小于首元素,则交换指针元素。重复此过程直到左右指针相遇,从相遇处进行二分。(直接对原数组进行操作,无需像归并排序额外开辟空间,再一样赋值回去
但存在交换操作(不稳定排序

和归并排序不同,第一步并不是直接分成前后两个序列,而是在分的过程中要保证相对大小关系。具体来说,第一步要是要把数列分成两个部分,然后保证前一个子数列中的数都小于后一个子数列中的数。

之后,维护一前一后两个指针 p 和 q,依次考虑当前的数是否放在了应该放的位置(前还是后)。如果当前的数没放对,比如说如果后面的指针 q 遇到了一个比 m 小的数,那么可以交换 p 和 q 位置上的数,再把 p 向后移一位。当前的数的位置全放对后,再移动指针继续处理,直到两个指针相遇。

其实,快速排序没有指定应如何具体实现第一步,不论是选择 m 的过程还是划分的过程,都有不止一种实现方法。

第三步中的序列已经分别有序且第一个序列中的数都小于第二个数,所以直接拼接起来就好了。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,e) for(int i=s;i<e;i++)
#define per(i,s,e) for(int i=s;i>e;i--)

void QuickSort(vector<int>&v, int left, int right){//quickSort函数的参数是排序数组和左右索引位置的集合 或
    int i,j,temp,t;//i,j是左右子序列的指针
    if(left>=right){
        return;
    }
    i=left; j=right; temp=v[left];
    while(i<j){//循环直到i或j达到中点位置 
        while(temp<=v[j]&&i<j) j--;//找到右子序列大于temp的元素的索引j
        while(temp>=v[i]&& i<j) i++;//找到左子序列小于temp的元素的索引i
        t=v[j]; v[j]=v[i]; v[i]=t;//将两个子序列中的元素交换到合并后的数组
    }
    v[left]=v[i]; v[i]=temp;//将中点元素放回数组中的位置 或将i设
    QuickSort(v,left,j-1);//递归调用函数以对左子序列进行排序
    QuickSort(v,j+1,right);//递归调用函数以对右子序列进行排序
}


int main() {
    vector<int> v={2,9,2,4,6,8,1};
    QuickSort(v,0,v.size()-1); //v=[2,41,2,4,6,8,1]->[2,4,2,41
    for(auto e:v)cout<<e;
    return 0;
}

在这里插入图片描述

总结:
在这里插入图片描述
冒泡排序:适用于数据量较小的情况,时间复杂度为 O(N^2),空间复杂度为 O(1),是一种稳定排序算法。

选择排序:适用于数据量较小的情况,时间复杂度为 O(N^2),空间复杂度为 O(1),是一种不稳定的排序算法。

插入排序:适用于数据量较小且基本有序的情况,时间复杂度为 O(N^2),空间复杂度为 O(1),是一种稳定排序算法。但对于基本有序的数据,插入排序效率最高。

归并排序:适用于数据量较大的情况,时间复杂度为 O(NlogN),空间复杂度为 O(N),是一种稳定排序算法。缺点是需要额外的空间开销。

快速排序:适用于数据量较大的情况,时间复杂度为 O(NlogN),空间复杂度为 O(logN),是一种不稳定的排序算法。缺点是在极端情况下可能会出现退化,效率大幅下降。

希尔排序:适用于数据量较大的情况,时间复杂度为 O(N^1.3 - N^2),空间复杂度为 O(1),是一种不稳定的排序算法。缺点是实现较为复杂。

堆排序:适用于数据量较大的情况,时间复杂度为 O(NlogN),空间复杂度为 O(1),是一种不稳定的排序算法。缺点是实现较为复杂。

计数排序:适用于值域范围较小的整数序列,时间复杂度为 O(N+K),空间复杂度为 O(K),是一种稳定排序算法。缺点是需要额外的空间开销。

桶排序:适用于分布比较均匀的数据,时间复杂度为 O(N),空间复杂度为 O(M),是一种稳定排序算法。缺点是对于分布不均匀的数据,效率不如快排和归并排序。

13.动态规划——最优子结构求解复杂问题

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其中3是动态规划dp 区别于 分治法的特点.

例.动态规划解决最短路径问题
暴力法:遍历每种可能的路径,选择最短的那条。
动态规划法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
目标就是S1-S2-S3-S4-S5-S6-S7的总距离Vk,n(S1,Sn)
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
反向递归求解 最优子结构,同时注意化简!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yuezero_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值