数据结构与算法

Posted jiangxiaobin1996

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法相关的知识,希望对你有一定的参考价值。

二叉树的遍历:

前序遍历(递归): LeetCode 144

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> vec;
    vector<int> preorderTraversal(TreeNode* root) {
        if(root!=NULL){
            vec.push_back(root->val);
            preorderTraversal(root->left);
            preorderTraversal(root->right);
        }
        return vec;
    }
};

非递归:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> vec;
        stack<TreeNode*> s;
        while(root || !s.empty()){
            if(root!=NULL){
                s.push(root);
                vec.push_back(root->val);
                root=root->left;
            }else{
                root=s.top();
                s.pop();
                root=root->right;
            }
        }
        return vec;
    }
};

中序遍历(递归):LeetCode 94

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> vec;
    vector<int> inorderTraversal(TreeNode* root) {
        if(root){
            inorderTraversal(root->left);
            vec.push_back(root->val);
            inorderTraversal(root->right);
        }
        return vec;
    }
};

非递归:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
       vector<int> vec;
        stack<TreeNode*> s;
        while(root || !s.empty()){
            if(root){
                s.push(root);
                root=root->left;
            }else{
                root=s.top();
                s.pop();
                vec.push_back(root->val);
                root=root->right;
            }
        }
        return vec;
    }
};

  后序遍历(递归): LeetCode 145

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> vec;
    vector<int> postorderTraversal(TreeNode* root) {
        if(root){
            postorderTraversal(root->left);
            postorderTraversal(root->right);
            vec.push_back(root->val);
        }
        return vec;
    }
};

  非递归:(大致解析:如果该结点的左右子树都为空或者左右子树中的一个为上一次访问的结点即该结点的右结点已经访问过了,则输出。否则将该结点的右结点和左结点压入栈中)

PS:这是其中一种解法,还有一种解法是对每一个结点增加一个isFirst的属性来判断该结点是否是第一次访问,如果是第二次访问则输出。否则访问该结点的右结点。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> vec;
        stack<TreeNode*> s;
        TreeNode* pre=NULL;
        if(root==NULL) return vec;
        else s.push(root);
        while(!s.empty()){
            root=s.top();
            if((root->left==NULL&&root->right==NULL) || (pre!=NULL&&(pre==root->left||pre==root->right))){
                s.pop();
                vec.push_back(root->val);
                pre=root;
            }else{
                if(root->right) s.push(root->right);
                if(root->left) s.push(root->left);
            }
        }
        return vec;
    }
};

图的遍历:

广度优先搜索(BFS):

queue<int> q;
int main()
{
    for(int i=0;i<6;i++){
        visit[i]=0;
    }
    for(int i=0;i<6;i++){    //如果有一点与其他点都不连接,遍历一遍确保访问到所有点
        if(visit[i] == 0){
            BFS(i);
        }
    }        
}

邻接矩阵存法 

void BFS(int v){
    cout<<v<<endl;
    visit[v]=1;
    q.push(v);
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=0;i<6;i++){
            if(a[x][i] == 1 && visit[i] == 0){
                cout<<i<<endl;
                visit[i]=1;
                q.push(i);
            }
        }
    }
}  

性能分析:

  邻接表存:每个点和每条边都要遍历一遍,算法时间复杂度为O(|V|+|E|)

  邻接矩阵存:算法时间复杂度为O(|V|²)

BFS算法求最短路径:

#define NF 1000 //无穷大值
queue<int> p;
int d[6]={NF,NF,NF,NF,NF,NF};
void BFS_MIN()
{
    int u=0;
    visit[u]=1;
    d[u]=0;
    p.push(u);
    while(!p.empty()){
        int x = p.front();
        p.pop();
        for(int i=0;i<6;i++){
            if(a[x][i] == 1 && visit[i] == 0){
                visit[i]=1;
                d[i]=d[x]+1;
                p.push(i);
            }
        }
    }
}

深度优先搜索(DFS):

void DFS(int u)
{
    cout<<u<<endl;
    visit[u]=true;
    for(int i=0;i<6;i++){
        if(a[u][i]==1 && visit[i]==false){
            DFS(i);
        }
    }
}

性能分析:

  邻接表:时间复杂度O(|V|+|E|)

  邻接矩阵:时间复杂度O(|V|²)

 拓扑排序(判断有无环):

kahn算法:

  1. 在有向图中选一个入度为零的点,并输出

  2. 从图中删除所有与该点相关的边

  3. 重复上述两步,直至所有顶点输出,或者当前图中不存在无前驱的顶点为止,后者代表我们的有向图是有环的,因此,也可以通过拓扑排序来判断一个图是否有环。

void kahn()
{
    stack<int> s;
    int r[6]; //记录每个点的入度
    for(int i=0;i<6;i++){
        r[i]=0;
    }

    //统计每个点的入度
    int num=0;
    for(int i=0;i<6;i++){
        num=0;
        for(int j=0;j<6;j++){
            if(a[j][i] == 1) num++;
        }
        r[i]=num;
    }

    //把入度为零的入栈
    for(int i=0;i<6;i++){
        if(r[i]==0) s.push(i);
    }

    //count用于计算输出点的个数
    int count=0;
    while(!s.empty()){
        int x=s.top();
        s.pop();
        cout<<x<<endl;
        for(int i=0;i<6;i++){
            if(a[x][i] == 1){
                r[i]--;
                if(r[i]==0) s.push(i);
            }
        }
        count++;
    }

    if(count == 6) cout<<"此图无环"<<endl;
    else cout<<"此图有环"<<endl;
}

深度优先搜索实现拓扑排序:

void DFS(int u)
{
    visit[u] = true;
    for(int i=0;i<6;i++){
        if(a[u][i]==1 && visit[i]==false){
            DFS(i);
        }
    }
    // 每次在递归结束时入栈
    // 即当前顶点没有指向其他顶点的边了,也就是一条路径的最后一个顶点
    s.push(u); 
}

之后只要输出栈就行了。

最小生成树:

Prim算法:

  1. 以一个点为起点

  2. 查找与该点直接相连的点的最小权值

  3. 将两个点看成一个整体,更新lowcost数组

  4. 重复二操作

void prim()
{
    bool visit[n];
    int low[n];
    int min_path=0;

    for(int i=0;i<n;i++){
        visit[i]=false;
        low[i]=a[0][i];
    }
    visit[0]=true;

    int flag=0;
    int min_val=0;
    for(int i=0;i<n;i++){
        min_val=NF;
        for(int j=0;j<n;j++){
            if(!visit[j] && low[j] <= min_val){
                min_val=low[j];
                flag=j;
            }
        }
        cout<<"flag:"<<flag<<endl;
        if(min_val == NF) break;
        min_path+=min_val;
        visit[flag]=true;
        for(int i=0;i<n;i++){
            //更新lowcost数组操作
            //比较原来整体到剩余各个点的权值与增加的点到各个点的权值的大小
            if(!visit[i] && a[flag][i] <= low[i]) low[i]=a[flag][i];
        }
    }
    cout<<min_path<<endl;
}

Kruskal算法:

  1.  对所有边的权值进行排序

  2. 选取最小的权值的边,若增加这条边后与已有点边形成环形则舍去

  3. 重复二操作

#include<bits/stdc++.h>
using namespace std;

int V;
int E;
struct edge{
    int u;
    int v;
    int val;
};
edge e[100];
int father[100];

// 初始化father数组
void init_union_find(int n)
{
    for(int i=0;i<n;i++){
        father[i]=i;
    }
}
// 查找新插入的点属于哪一个集合
int find(int x)
{
    return father[x] == x ? x : find(father[x]);
}
bool cmp(edge e1,edge e2)
{
    return e1.val < e2.val;
}
void Kruskal()
{
    sort(e,e+E,cmp);
    init_union_find(V);
    int min_path=0;
    int count=0; //计数,添加的边数
    for(int i=0;i<E;i++){
        edge ed = e[i];
        int x=find(ed.u);
        int y=find(ed.v);
        if(x!=y){
            min_path+=ed.val;
            father[x]=y; // 将x并入y集合
            count++;
        }
    }
    // 若添加的边数小于点数-1则不是一棵生成树
    if(count < V-1) min_path=0;
    cout<<min_path<<endl;
}

main(void)
{

    cin>>V>>E;
    for(int i=0;i<E;i++){
        cin>>e[i].u>>e[i].v>>e[i].val;
    }

    Kruskal();
}

最短路径:

Dijkstra算法:

  个人感觉和Prim算法非常相似,或者说是几乎一样,唯一的区别是在更新最小权值数组的操作上。prim算法是针对整体的:每新加入一个点,就把这些点看成一个整体,计算的是整体到各个点的路径长度。Dijkstra算法是从一定点出发,每次计算的都是各个点到该定点的路径长度。

  1. 选取一个定点

  2. 比较各点到该定点的路径的最小值

  3. 将最小值对应的点加入的路径中

  4. 更新最小权值数组(该数组表示定点到每个点的最小路径长度)

  5. 重复二操作

#include<bits/stdc++.h>
#define INF 10000
using namespace std;
int V,E;
int val[5][5];
int lowcost[5];
bool visit[5];

void Dijkstra(int s)
{
    //初始化lowcost表
    for(int i=0;i<V;i++){
        lowcost[i]=val[s][i];
    }
    visit[s]=true;
    int flag=0;
    int min_val=0;
    for(int i=0;i<V;i++){
        min_val=INF;
        for(int j=0;j<V;j++){
            // 查找lowcost中最小的值并记录
            if(!visit[j] && lowcost[j] <= min_val){
                min_val=lowcost[j];
                flag=j;
            }
        }
        cout<<flag<<":"<<min_val<<endl;
        visit[flag]=true;
        for(int t=0;t<V;t++){
            // 这两条语句作用相同
            // 更新lowcost数组
            // 比较原来从0到该点的路径和增加一点后经过这些点到该点的路径长度
            //if(!visit[t] && val[flag][t]+min_val<=lowcost[t]) lowcost[t]=val[flag][t]+min_val;
            lowcost[t]=min(lowcost[t],val[flag][t]+min_val);
        }
    }
}

main(void)
{
    int a,b,c;
    cin>>V>>E;
    // 初始化val表
    for(int i=0;i<V;i++){
        for(int j=0;j<V;j++){
            val[i][j]=INF;
        }
    }
    // 初始化visit表
    for(int i=0;i<V;i++){
        visit[i]=false;
    }
    // val表赋值
    for(int i=0;i<E;i++){
        cin>>a>>b>>c;
        val[a][b]=c;
    }
    Dijkstra(0);
}

Floyd算法:

#include<bits/stdc++.h>
#define INF 10000
using namespace std;
int val[100][100];
int used[100][100];
int V;
int E;


void Floyd()
{
    int k;
    for(int i=0;i<V;i++){
        for(int j=0;j<V;j++){
            used[i][j]=0;
        }
    }

    for(k=0;k<V;k++){ //经过的点
        for(int i=0;i<V;i++){
            for(int j=0;j<V;j++){
                if(k!=i && k!=j && i!=j){
                    if(val[i][j]>=val[i][k]+val[k][j]){
                        //关键步骤
                        val[i][j]=min(val[i][j],val[i][k]+val[k][j]);
                        //用于确定每次操作改变的值和对应的k值
                        used[i][j]=k+1;
                    }
                }
            }
        }

        cout<<endl;
        for(int i=0;i<V;i++){
            for(int j=0;j<V;j++){
                if(j == V-1) cout<<val[i][j]<<endl;
                else cout<<val[i][j]<<‘ ‘;
            }
        }
        cout<<endl;
        for(int i=0;i<V;i++){
            for(int j=0;j<V;j++){
                if(j == V-1) cout<<used[i][j]<<endl;
                else cout<<used[i][j]<<‘ ‘;
            }
        }
    }
}

int main()
{
    int a,b,c;
    cin>>V;
    cin>>E;
    for(int i=0;i<V;i++){
        for(int j=0;j<V;j++){
            if(i!=j) val[i][j]=INF;
            else val[i][j]=0;
        }
    }

    for(int i=0;i<E;i++){
        cin>>a>>b>>c;
        val[a][b]=c;
    }
    Floyd();
}

KMP:

#include<bits/stdc++.h>
using namespace std;

main(void)
{
    string a = "BBC ABCDAB ABCDABCDABDE";
    string b = "ABCDABD";
    cout<<a<<endl;
    cout<<b<<endl;
    int next[b.length()];


    //next数组的生成
    //next数组是KMP中比较难以理解的,实际就是求对应目标串的最长相同前后缀
    //如ABCDAB中AB就是最长的前后缀,又如ABCDA中A就是最长的前后缀
    int k = -1;
    int j = 0;
    next[0] = -1;
    while(j < b.length()-1){
        if (k == -1 || b[k] == b[j]) {
            ++k;
            ++j;
            next[j] = k;
        }else {
            k = next[k];
        }
    }

    for(int i=0;i<b.length();i++){
        cout<<next[i]<<‘ ‘;
    }
     cout<<endl;

    //a,b两个字符串匹配
    int i = 0;
    j = 0;
    while(i<a.length() && j<(int)b.length()){
        if (j == -1 || a[i] == b[j]) {
            i++;
            j++;
        }else{
            j = next[j];
        }

    }
    if (j == b.length()) {
        cout<<i-j<<endl;
    }else {
        cout<<"-1"<<endl;
    }
}

插入排序:

void InsertSort(int a[],int n)
{
    int temp=0;
    int i,j;
    for(i=1;i<n;i++){               
        if(a[i] < a[i-1]){           //若a[i]的关键码小于其前驱,需要将a[i]插入有序表
            temp = a[i];             //记录a[i]
            for(j=i-1;j>=0;j--){
                if(a[j] > temp){
                    a[j+1] = a[j];   //向后挪位
                }else{
                    break;
                }
            }
            a[j+1] = temp;
        }
    }

    for(int i=0;i<n;i++) cout<<a[i]<<‘ ‘;
}

Shell排序:

void ShellSort(int a[],int n)
{
    //Shell排序类似于调整步长的插入排序
    int i,j;
    int temp=0;
    int dk;
    for(dk=n/2;dk>=1;dk/=2){             //步长的变化
        for(i=dk;i<n;i++){
            if(a[i] < a[i-dk]){
                temp = a[i];
                for(j=i-dk;j>=0;j-=dk){  //子序列的插入排序
                    if(a[j] > temp){
                        a[j+dk] = a[j];
                    }else{
                        break;
                    }
                }
                a[j+dk] = temp;
            }
        }
    }

    for(int i=0;i<n;i++) cout<<a[i]<<‘ ‘;
}

冒泡排序:

void BubbleSort(int a[],int n)
{
    int i,j;
    int temp=0;
    for(i=0;i<n;i++){
        for(j=n-1;j>i;j--){
            if(a[j] < a[j-1]){
                temp = a[j];
                a[j] = a[j-1];
                a[j-1] = temp;
            }
        }
    }
    for(int i=0;i<n;i++) cout<<a[i]<<‘ ‘;
}

快速排序:

int Partition(int a[],int low,int high)
{
    int first = low;
    int pivot = a[low];
    low = low + 1;
    while(low <= high){
        while(low <= high && a[high] > pivot) high--;  //从最右边查找第一个比轴值小的值
        while(low <= high && a[low] < pivot) low++;    //从最左边查找第一个比轴值大的值
        if(low < high){
            swap(a[low++],a[high--]);                  //交换这两个值(交换完两个指针移动一位)      
        }else{
            low ++;
        }
    }
    swap(a[high],a[first]);                            //交换high处值和轴值
    return high;
}


void QuickSort(int a[],int low,int high)
{
    if(high <= low) return;
    int pivotpos = Partition(a,low,high);
    QuickSort(a,low,pivotpos-1);
    QuickSort(a,pivotpos+1,high);
}

简单选择排序:

void SelectSort(int a[],int n)
{
    int temp = a[0];
    int flag = 0;
    for(int i=0;i<n;i++){
        temp = a[i];
        for(int j=i;j<n;j++){
            //查找从i位开始的序列的最小值
            if(a[j] <= temp){
                temp = a[j];
                flag = j;
            }
        }
        swap(a[i],a[flag]); 
    }
    for(int i=0;i<n;i++) cout<<a[i]<<‘ ‘;
}

堆排序: 以最大堆为例

void BuildMaxHeapSort(int a[],int len)
{
    for(int i=len-1;i>=0;i--){
        if(a[i] > a[(i-1)/2]){
            swap(a[i],a[(i-1)/2]);    //如果儿子结点大于父节点,交换
        }
    }
    cout<<a[0]<<endl;                 //提取树根元素
}

void HeapSort(int a[],int n)
{
    BuildMaxHeapSort(a,n);
    for(int i=n-1;i>0;i--){
        a[0] = a[i];                  //把最后一个结点放在树根处
        BuildMaxHeapSort(a,--n);      //长度--
    }
}

归并排序:

void Merge(int a[],int low,int mid,int high)
{
    int i,j,k;
    int length = high - low + 1;
    int *b = (int *)malloc((length+1)*sizeof(int));     //辅助数组(malloc增加stdlib.h的头文件)
    for(int k=low;k<=high;k++){
        b[k] = a[k];
    }
    for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){
        if(b[i] <= b[j]){                //比较两个子序列中的元素
            a[k] = b[i++];               //把小的那个元素赋值到数组a中
        }else{
            a[k] = b[j++];
        }
    }
    while(i<=mid) a[k++] = b[i++];      //若第一个表未检测完,复制到数组a中
    while(j<=high) a[k++] = b[j++];     //若第二个表未检测完,复制到数组a中
}

void MergeSort(int a[],int low,int high)
{
    if(low >= high) return;
    int mid = (low + high) / 2;
    MergeSort(a,low,mid);
    MergeSort(a,mid+1,high);
    Merge(a,low,mid,high);
}

  

 

 

 

 

以上是关于数据结构与算法的主要内容,如果未能解决你的问题,请参考以下文章

片段(Java) | 机试题+算法思路+考点+代码解析 2023

编程思想与算法

常用编程思想与算法

伪代码

机器学习3_EM算法与混合高斯模型

可以解密加密数据的片段吗?