重学数据结构与算法

学习数据结构与算法的目的:
优化时间复杂度与空间复杂度 优化时间复杂度与空间复杂度 优化时间复杂度与空间复杂度
教程总纲: 暴力解法(模拟)算法优化(递归/二分/排序/DP)时刻转换(数据结构)
重学数据结构与算法_第1张图片

    • 1.时间复杂度的核心方法论
    • 2.增删查——选取数据结构的基本方法
    • 3.线性表——如何完成基本增删查
    • 4.栈——先进后出的增删查
    • 5.队列——先进先出的增删查
    • 6.数组——基于索引的查找
    • 7.字符串——字符串匹配与操作
    • 8.树&二叉树——分支与层次关系
    • 9.哈希表——高效查找的利器
    • 10.递归——解决汉诺塔问题
    • 11.分治——利用分治快速完成数据查找
    • 12.排序——经典排序算法解析

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

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

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

重学数据结构与算法_第2张图片

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

重学数据结构与算法_第3张图片
时刻转换:选用合适的数据结构,进一步降低时间复杂度

例.输入数组 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;
}

重学数据结构与算法_第4张图片

优化思想:如何仅用单层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.增删查——选取数据结构的基本方法

当你不知道用什么数据结构的时候:
分析需要对数据进行了哪些操作,根据数据操作,选取合适的数据结构 分析需要对数据进行了哪些操作,根据数据操作,选取合适的数据结构 分析需要对数据进行了哪些操作,根据数据操作,选取合适的数据结构
重学数据结构与算法_第5张图片

还用上面的例子介绍:

对于统计次数最多的元素,我们需要对数据结构进行以下操作:
重学数据结构与算法_第6张图片
具体的:
重学数据结构与算法_第7张图片
所以
重学数据结构与算法_第8张图片
重学数据结构与算法_第9张图片

重学数据结构与算法_第10张图片

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

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

重学数据结构与算法_第11张图片
单向链表:
重学数据结构与算法_第12张图片
循环链表:
重学数据结构与算法_第13张图片
双向链表:
重学数据结构与算法_第14张图片
双向循环链表:
重学数据结构与算法_第15张图片

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

增加操作重学数据结构与算法_第16张图片
删除操作
重学数据结构与算法_第17张图片
查找操作:
重学数据结构与算法_第18张图片
重学数据结构与算法_第19张图片
总结:
链表的查找速度慢 ( 无法用 i n d e x ) O ( n ) ,但插入和删除 ( 改变指针 ) 方便 O ( 1 ) 链表的查找速度慢(无法用index)O(n),但插入和删除(改变指针)方便O(1) 链表的查找速度慢(无法用index)O(n),但插入和删除(改变指针)方便O(1)

重学数据结构与算法_第20张图片
重学数据结构与算法_第21张图片

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

例1.

重学数据结构与算法_第22张图片
为此,我们使用3个指针prev、curr、next,分别指向 新链表头节点、旧链表转换节点、旧链表转换节点的下一个,完成旧链表向链表逐个节点的转换。
重学数据结构与算法_第23张图片
重学数据结构与算法_第24张图片

重学数据结构与算法_第25张图片

#include
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.
重学数据结构与算法_第26张图片
重学数据结构与算法_第27张图片
slow走1步,fast走两步。(因为fast一次走两步,所以要防止fast到fast.next.next为空,所以while的判断条件是3个)
fast到达终点时,slow到达中点。

#include
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.
重学数据结构与算法_第28张图片
基本思想是利用两个指针,一个快指针和一个慢指针,分别从链表头部开始遍历,快指针每次走两步,慢指针每次走一步,若快指针追上了慢指针,则说明链表存在环路;否则,当快指针到达链表尾部时,结束遍历,slow永远不可能和fast相等,链表不存在环路。

#include
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.栈——先进后出的增删查

重学数据结构与算法_第29张图片
重学数据结构与算法_第30张图片
重学数据结构与算法_第31张图片
顺序栈:
推荐:用vector模拟栈时,仅允许在线性表尾部(栈顶)插入删除数据push_back()pop_back()

不推荐:也可以用数组模拟。
重学数据结构与算法_第32张图片
在这里插入图片描述
重学数据结构与算法_第33张图片

链栈: 不需要头指针,进维护一个栈顶top指针。
重学数据结构与算法_第34张图片
重学数据结构与算法_第35张图片
重学数据结构与算法_第36张图片
例1.
重学数据结构与算法_第37张图片
重学数据结构与算法_第38张图片

#include
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.
重学数据结构与算法_第39张图片
重学数据结构与算法_第40张图片
重学数据结构与算法_第41张图片

#include
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();
}

总结:
重学数据结构与算法_第42张图片

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

重学数据结构与算法_第43张图片
重学数据结构与算法_第44张图片

头指针front,尾指针rear

重学数据结构与算法_第45张图片

链队: 头节点仅用来表示队列(data=number),不用了存储数据
重学数据结构与算法_第46张图片

重学数据结构与算法_第47张图片
重学数据结构与算法_第48张图片
重学数据结构与算法_第49张图片
重学数据结构与算法_第50张图片
头节点的意义:给空链表的front 和rear指针一个指向,防止变成野指针。
重学数据结构与算法_第51张图片

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

循环队列:
重学数据结构与算法_第57张图片
重学数据结构与算法_第58张图片
重学数据结构与算法_第59张图片

例1.
重学数据结构与算法_第60张图片
重学数据结构与算法_第61张图片


总结:
重学数据结构与算法_第62张图片
重学数据结构与算法_第63张图片

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

重学数据结构与算法_第64张图片
重学数据结构与算法_第65张图片

重学数据结构与算法_第66张图片
增加:
重学数据结构与算法_第67张图片
删除:
重学数据结构与算法_第68张图片
查找;
重学数据结构与算法_第69张图片
重学数据结构与算法_第70张图片
总结:
重学数据结构与算法_第71张图片

重学数据结构与算法_第72张图片
重学数据结构与算法_第73张图片

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

重学数据结构与算法_第74张图片
重学数据结构与算法_第75张图片
重学数据结构与算法_第76张图片
重学数据结构与算法_第77张图片
插入:
重学数据结构与算法_第78张图片
删除:
重学数据结构与算法_第79张图片
字符串匹配:
重学数据结构与算法_第80张图片

暴力匹配:
重学数据结构与算法_第81张图片
重学数据结构与算法_第82张图片
例题. 可以暴力也,可以动态规划
重学数据结构与算法_第83张图片

暴力法:
重学数据结构与算法_第84张图片

#include
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
}

例题.
重学数据结构与算法_第85张图片

#include
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
重学数据结构与算法_第86张图片

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

重学数据结构与算法_第87张图片
重学数据结构与算法_第88张图片
重学数据结构与算法_第89张图片
重学数据结构与算法_第90张图片
重学数据结构与算法_第91张图片
重学数据结构与算法_第92张图片
非完全二叉树,使用顺序存储会浪费大量的存储空间!
重学数据结构与算法_第93张图片
重学数据结构与算法_第94张图片
重学数据结构与算法_第95张图片
重学数据结构与算法_第96张图片
递归实现二叉树的前、中、后序遍历

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<<" ";
}

重学数据结构与算法_第97张图片

二叉查找树(二次排序树): 左小右大,左子树上所有结点的关键字均小于根结点的关键字;右子树上的所有结点的关键字均大于根结点的关键字。中序遍历是有序数列!
重学数据结构与算法_第98张图片
重学数据结构与算法_第99张图片
利用二次排序树的性质,可以实现二分查找,加快查找速度!!!
重学数据结构与算法_第100张图片
二叉排序树的插入:
重学数据结构与算法_第101张图片
重学数据结构与算法_第102张图片
重学数据结构与算法_第103张图片
重学数据结构与算法_第104张图片
重学数据结构与算法_第105张图片
重学数据结构与算法_第106张图片
二叉排序树删除:
重学数据结构与算法_第107张图片
重学数据结构与算法_第108张图片
重学数据结构与算法_第109张图片
重学数据结构与算法_第110张图片

例题.
重学数据结构与算法_第111张图片
可以暴力搜索,也可以用字典树,层次遍历到叶子节点的路径。

重学数据结构与算法_第112张图片
重学数据结构与算法_第113张图片
重学数据结构与算法_第114张图片
重学数据结构与算法_第115张图片
例题.层次遍历,维护OPEN表(队列),不断扩展队首子节点,加入队列。
重学数据结构与算法_第116张图片

#include
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 
#include 
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;
}

重学数据结构与算法_第117张图片

总结:
重学数据结构与算法_第118张图片

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

重学数据结构与算法_第119张图片
重学数据结构与算法_第120张图片
重学数据结构与算法_第121张图片
重学数据结构与算法_第122张图片
在这里插入图片描述

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

重学数据结构与算法_第123张图片

哈希函数设计: 下面几个方法都可能出现哈希冲突
重学数据结构与算法_第124张图片
重学数据结构与算法_第125张图片
冲突解决:

线性探测法(沿占用地址逐个向下遍历寻找未占用的地址存放)
重学数据结构与算法_第126张图片
重学数据结构与算法_第127张图片

链地址法(将相同哈希地址的记录存放在同一条链表上)
重学数据结构与算法_第128张图片
总结:优点(CUDR飞快)、缺点(处理输入顺序敏感的问题时,会破坏序列的构造顺序)
重学数据结构与算法_第129张图片

重学数据结构与算法_第130张图片
如C++的map
重学数据结构与算法_第131张图片
例子.
重学数据结构与算法_第132张图片
重学数据结构与算法_第133张图片
重学数据结构与算法_第134张图片
例题.
重学数据结构与算法_第135张图片
重学数据结构与算法_第136张图片

#include
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 
#include 
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;
}

总结:
重学数据结构与算法_第137张图片

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

重学数据结构与算法_第138张图片
重学数据结构与算法_第139张图片
重学数据结构与算法_第140张图片
重学数据结构与算法_第141张图片
例子.中序遍历
重学数据结构与算法_第142张图片
重学数据结构与算法_第143张图片
重学数据结构与算法_第144张图片
例子.汉诺塔问题
重学数据结构与算法_第145张图片
重学数据结构与算法_第146张图片
重学数据结构与算法_第147张图片
重学数据结构与算法_第148张图片
总结:
重学数据结构与算法_第149张图片

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

重学数据结构与算法_第150张图片
重学数据结构与算法_第151张图片

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

重学数据结构与算法_第152张图片

例子.
重学数据结构与算法_第153张图片

重学数据结构与算法_第154张图片
在这里插入图片描述
重学数据结构与算法_第155张图片
重学数据结构与算法_第156张图片
可以递归实现,也可以循环实现

#include
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;
}

例题.
重学数据结构与算法_第157张图片

重学数据结构与算法_第158张图片

总结:
重学数据结构与算法_第159张图片
重学数据结构与算法_第160张图片

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

重学数据结构与算法_第161张图片
重学数据结构与算法_第162张图片
冒泡排序: 相邻元素两两比较,逆序对交换,每轮将1个大的元素交换到最后,经过多轮迭代完成排序。稳定:元素相等时不做交换
重学数据结构与算法_第163张图片
重学数据结构与算法_第164张图片

#include
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]);
        }
    }
}

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

重学数据结构与算法_第165张图片
重学数据结构与算法_第166张图片

#include
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元素放在合适的位置
    }
}

重学数据结构与算法_第167张图片
归并排序: 将待排序序列从中点位置分成左右两个子序列,分别递归调用归并排序函数,直到子序列长度为1。对两个已经有序的子序列进行合并,得到一个新的有序序列。 稳定排序重学数据结构与算法_第168张图片


你可能感兴趣的:(算法,数据结构,排序算法)