数据结构和简单算法思想

2023-11-12

只为自己学习进行一下记录

虽然之前上了一些关于数据结构、算法之类的课,但之前都没有怎么搞懂,尤其是算法里面的一些算法思想,现在看能不能补上,就是一些大佬的算法指导,刷LeetCode的一些题,回看之前的书上面的重点。

教材是清华大学出版社的数据结构(C语言版)、计算机算法分析与设计(第四版)

1.算法复杂度分析
1.时间复杂度

大 O 时间复杂度表示法( T(n) =O(f(n)) ) 实际上并不具体表示代码真正的执行时间,而是表示 代码执行时间随数据规模增长的变化趋势,所以也叫 渐进时间复杂度,简称 时间复杂度

由于 时间复杂度 描述的是算法执行时间与数据规模的 增长变化趋势,所以 常量、低阶、系数 实际上对这种增长趋势不产生决定性影响,所以在做时间复杂度分析时 忽略 这些项

例:

function cal(m, n) {
  let sum_3 = 0;
   let i = 1;
   let j = 1;
   for (; i <= m; ++i) {
     j = 1; 
     for (; j <= n; ++j) {
       sum_3 = sum_3 +  i * j;
     }
   }
}

以上代码也是求和,两层 for 循环 ,求 sum_3 的数据规模为 m 和 n,所以时间复杂度为 O(m*n)。

时间复杂度可以分为:

  • 最好情况时间复杂度(best case time complexity):在最理想的情况下,执行这段代码的时间复杂度。
  • 最坏情况时间复杂度(worst case time complexity):在最糟糕的情况下,执行这段代码的时间复杂度。
  • 平均情况时间复杂度(average case time complexity),用代码在所有情况下执行的次数的加权平均值表示。也叫 加权平均时间复杂度 或者 期望时间复杂度
  • 均摊时间复杂度(amortized time complexity): 在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上。基本上均摊结果就等于低级别复杂度

一般我们最关心的还是最坏情况下的时间复杂度或平均复杂度

O(1) < O(logn) < (n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)

2.空间复杂度

类比一下,空间复杂度全称就是 渐进空间复杂度(asymptotic space complexity),表示 算法的存储空间与数据规模之间的增长关系

例:

function print(n) {
 const newArr = []; // 第 2 行
 newArr.length = n; // 第 3 行
  for (let i = 0; i <n; ++i) {
    newArr[i] = i * i;
  }

  for (let j = n-1; j >= 0; --j) {
    console.log(newArr[i])
  }
}

跟时间复杂度分析一样,我们可以看到,第 2 行代码中,我们申请了一个空间存储变量 newArr ,是个空数组。第 3 行把 newArr 的长度修改为 n 的长度的数组,每项的值为 undefined ,除此之外,剩下的代码都没有占用更多的空间,所以整段代码的空间复杂度就是 O(n)。


2.数据结构
1.KPM算法和next数组求值

这是一个进行字符串匹配的算法,原理是求一个字符串的某一个字符的前面的字符串最大相等的前缀和后缀

kmp算法为

int kmp(string s,string t,int pos){//s为主串,t为模式串
	i = pos;
	j=1;
	while(i<s[0] && j<t[0]){//这两个0的位置存储的是string的长度
		if(j==0 || s[i] == t[j])	{++i;++j}//j=0表示主串第i个字符和模式串第1个字符不匹配,需从第i+1个字符从新匹配
		else	j = next[j];
	}
	if(j>t[0]) return i-t[0]; // 匹配成功
	else return 0;
}

求next数组的算法是仿照kmp的算法来的,i不进行后退,只有模式串滑动。因为求next也就是主串和模式串都是自己,可进行模仿kmp

void get_next(string t,int next[]){
	i=1; j=0; next[1]=0;
	while(i<t[0]){
	if(j==0 || t[i] == t[j]){++i;++j;next[i]=j}	
	else	j = next[j];
	}
}

核心就是next[i]=j,先进行++是因为前面相等,则后面一个字符的next就是在if里相等的最后一个字符j的后面一个字符。符合前缀等于后缀的原则。

改进后的next算法

void get_nextval(string t,int nextval[]){
	i=1; j=0; nextval[1]=0;
	while(i<t[0]){
	if(j=0 || t[i] == t[j]){
		++i;j++;
		if(t[i] != t[j])	nextval[i]=j;
		else	nextval[i] = nextval[j];
	}
	else j = nextval[j];
	}
}
2.树

一般研究二叉树

1.第i层至多有2^(i-1)个节点

2.深度为k的二叉树至多有2^k-1个节点

3.任何一颗二叉树t,若其叶子节点数为N0,度为2的节点数为N2,则N0=N2+1

​ 对于完全二叉树,就是对每一个节点,若有右孩子,则必有左孩子,且右分支的子孙的最大层次为l,则其左分支下的子孙的最大层次必为l或l+1.就是节点从左到右排顺序,这个顺序不能断。

4.有n个节点的完全二叉树的深度为log2 n向下取整+1

5.对任意节点i,若2i>n,则节点i无左孩子;如果有左孩子,其左孩子必是节点2i

6.若2i+1>n,则节点i无右孩子;如果有右孩子,其右孩子必是节点2i+1

7.i=1,则为根节点,若i>1,则双亲节点为i/2向下取余

二叉树可使用二叉链表进行存储,例如lchild | data | rchild

遍历

先序,中序,后序。为根节点在查序中的位置

//先序的递归算法
Status preorderTraverse(Bitree t,Status(* visit)(TElemType e)){
    status printelement(telemType e){
        printf(e);	//打印输出节点,	调用实例preorderTraverse(t,printelement);
        return ok;
    }
    if(t){
        if(visit(t->data))
            if(preorderTraverse(t->lchild,visit))
                if(preorderTraverse(t->rchild,visit)) return ok;
        return error;
    }else return ok;
}

//二叉树数据结构 js语法
function TreeNode(val) {
      this.val = val;
      this.left = this.right = null;
}

function DFStraval(node){  
        if(!node) return 0;
            console.log(node.val);
            traval(node.left);
            traval(node.right);
        
}

function BFStraval(node){ 
    const queue = [root];
    while(queue.length){
        let len  = queue.length;
        while(len){
            let node = queue.shift();
            console.log(node.val);
            node.left&&queue.push(node.left);
            node.right&&queue.push(node.right);
            len--;
        }
    }
}
3.图

图的邻接表存储

图的遍历

深度优先,广度优先

//深度优先遍历
Boolean visited[MAX]; //访问标志数组,初始值为false,当此节点被访问了,对应的visited[i]变为true
Status(*Visitedfunc)(int v)); //函数变量
void DFSTraverse(Graph G,Status(*visit)(int v)){
    VisitedFunc = Visit;
    for(v=0;v<G.vexnum;v++){visited[v] = false}	//初始化
    for(v=0;v<G.vexnum;v++){
        if(!visited[v]) DFS(G,v); //对未访问过的节点调用DFS
    }
}
DFS(Graph G,int v){
    visited[v] = true;	VisitFunc(v);	//输出v节点
    //FirstAdjVex返回v的第一个邻接节点,NextAdjVex返回v的相对于w的下一个节点,若w是v的最后一个节点,返回空
    for(w=FirstAdjVex(G,v);w>0;w=NextAdjVex(G,v,w)){
        if(!visted[w])	DFS(G,w);	//对v的未访问过的邻接节点w递归调用DFS
    }
}

最小生成树

稀疏图用Kruskal,稠密图用Prim

n个节点至少需要n-1条边连通,但边上还有权值,选择权值之和最小的n-1条边,可能不唯一

//最小生成树,普里姆算法,最小生成树不断壮大的过程,选顶点.使用邻接矩阵存储的网
//不断寻找与逐渐生成的最小生成树相连的边权最小的点加入
void MiniSpanTree_prim(MGraph G ,vertexType u){
    //从第u个顶点出发构造最小生成树
    //closedge记录从顶点集U到V-U的代价最小的边的辅助数组定义。就是未加入的节点和这棵生成树之间的权值
    struct {
        VertexType adjvex;//存储该边依附在顶点集U中的顶点
        VRtype lowcost;//存储在该边上的权值
    }closedge[MAX_VERTEX_NUM]
        k = LocateVex(G,u);	//开始的顶点的位置
    for(j=0;j<G.vexnum;j++){	//辅助数组初始化,lowcost为开始节点u与其他节点之间的权值,权值可为无穷大
        if(j != k) closedge[j] = {u,G.arc[k][j].adj} //{adjvex,lowcost}
    }
    closedge[k].lowcost = 0;	//初始U={u}
    for(i=1;i<G.vexnum;i++){	//选择剩下的G.vexnum-1个顶点
        k=minimum(closedge)//选择一个在closedge里边权值最小的顶点的序号,将其顶点和边加入最小生成树        
        printf(closedge[k].adjvex,G.vexs[k]);	//输出边
        closedge[k].lowcost = 0;	//第K顶点并入U集
        for(j=0;j<G.vexnum;j++){
            //新顶点并入U后,重新选择最小边。就是更新closedge里面的lowcost的值
            if(G.arcs[k][j].adj<closedge[j].lowcost)	
                //权值比原有的lowcost小,更新。	注意是k节点和其他节点之间的权值与之前节点的权值进行比较
                closedge[j] = {G.vexs[k],G.arcs[k][j].adj};
        }
    }
}

另一个就是Kruskal算法,即连通分量(即不能形成环)不断合并的过程。在开始就将n个顶点加入U,选边。

每次选择边权最小的边链接两个端点是kruskal的规则,并实时判断两个点之间有没有间接联通形成环。

参考链接

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,tot=0,k=0;//n端点总数,m边数,tot记录最终答案,k已经连接了多少边 
int fat[200];//记录集体老大。
//并查集就是一个用双亲表示法所表示的森林,我们可以利用这个结构来查找某一个顶点的双亲,进而找到根结点。这样,我们就能判断某两个顶点是否同源,在图中的表现就是加上这条边后会不会形成环。并查集以顶点为基准,有几个顶点,就有几项。
struct node
{
	int from,to,dis;//结构体储存边,dis为权值 
}edge[2000];

bool cmp(const node &a,const node &b)//sort排序(当然你也可以快排) 
{
	return a.dis<b.dis;
}
int father(int x)//找集体老大,并查集的一部分,就是寻找这个顶点的根 
{
	if(fat[x]!=x)	//如果这不是根,就继续向上查找,直至找到根
	return father(fat[x]);
	else return x;
}
void unionn(int x,int y)//加入团体,并查集的一部分 
{
	//fat[father(x)]=father(y);	//这感觉有些不能理解,甚至fat[x]=y好像也行
    fat[x]=father(y);	//这样也行
}
int main()
{
	scanf("%d%d",&n,&m);//输入点数,边数 
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].dis);//输入边的信息 
	}
	for(int i=1;i<=n;i++) fat[i]=i;//自己最开始就是自己的老大(初始化),自己的根就是自己
	sort(edge+1,edge+1+m,cmp);//按权值排序(kruskal的体现) 
	for(int i=1;i<=m;i++)//从小到大遍历 
	{
		if(k==n-1) break;//n个点需要n-1条边连接 
		if(father(edge[i].from)!=father(edge[i].to))//假如不在一个团体,其根不一样,就是判断形不形成环 
		{
			unionn(edge[i].from,edge[i].to);//加入这个根的团体 
            printf("from=%d to=%d \n",edge[i].from,edge[i].to);//打印加入的边的路径
			tot+=edge[i].dis;//记录边权 
			k++;//已连接边数+1 
		}
	}
	printf("%d",tot);
	return 0;
}

//测试用例
//6 7						
//0 2 1
//2 5 4
//5 3 2
//2 1 5
//1 4 3
//4 5 6
//0 4 3

拓扑排序

在有向图中选一个没有前驱的顶点(入度为0)输出,在图中删除该顶点和所有以它为尾的弧(也就是边)

可通过拓扑排序判断网中是否存在环,若所有的顶点都在拓扑排序序列中,则不存在环

//拓扑排序,判断是否有环
Status topologicalSort(AlGraph G){
    findinDegree(G,indegree);	//对各个顶点求入度 indegree[0……vexnum-1]
    InitStack(S);
    for(i=0;i<G.vexnum;++i){
        if(!indegree[i]) Push(S,i);	`//入度为0的顶点入栈
    }
    count = 0;	//对输出顶点计数
    while(!StackEmpty(S)){
        Pop(S,i);
        printf(i,G.vertices[i].data);	++count;	//输出i号顶点
        for(p=G.vertices[i].firstarc; p;p=p->nextarc){	//判断这个输出顶点的邻接点
            k = p->adjvex;	//对i号顶点的每个邻接点的入度-1
            if(!(--indegree[k]))	Push(S,k);	//若入度为0,则入栈
        }
    }
    if(count<G.vexnum)	return error;	//该有向图有回路
    else return Ok;
}

单源最短路径

参考此链接

// 单源最短路径:Dijkstra 算法 

#include <iostream>

using namespace std;

int  matrix[100][100]; // 邻接矩阵
bool visited[100];     // 标记数组
int  dist[100];        // 源点到顶点 i 的最短距离
int  path[100];        // 记录最短路的路径
int  source;           // 源点
int  vertex_num;       // 顶点数
int  edge_num;         // 边数

void Dijkstra(int source)
{
    memset(visited, 0, sizeof(visited));  // 初始化标记数组
    visited[source] = true;	//开始节点被访问
    for (int i = 0; i < vertex_num; i++)
    {
        dist[i] = matrix[source][i];	//开始节点到其他节点之间的权值
        path[i] = source;
    }

    int min_cost;        // 权值最小
    int min_cost_index;  // 权值最小的下标

    for (int i = 1; i < vertex_num; i++)  // 找到源点到另外 vertex_num-1 个点的最短路径
    {
        min_cost = INT_MAX;	//先设为最大值

        for (int j = 0; j < vertex_num; j++)
        {
            if (visited[j] == false && dist[j] < min_cost)  // 先找到与之相连的权值最小的节点
            {
                min_cost = dist[j];
                min_cost_index = j;
            }
        }

        visited[min_cost_index] = true;  // 该点已找到,进行标记

        for (int j = 0; j < vertex_num; j++)  // 更新 dist 数组
        {
            if (visited[j] == false &&	//表示这个节点还未被访问
                matrix[min_cost_index][j] != INT_MAX &&  // 确保两点之间有边
                matrix[min_cost_index][j] + min_cost < dist[j])	//**算法核心
            {
                dist[j] = matrix[min_cost_index][j] + min_cost;
                path[j] = min_cost_index;
            }
        }
    }
}

int main()
{
    cout << "请输入图的顶点数(<100):";
    cin >> vertex_num;
    cout << "请输入图的边数:";
    cin >> edge_num;

    for (int i = 0; i < vertex_num; i++)
        for (int j = 0; j < vertex_num; j++)
            matrix[i][j] = (i != j) ? INT_MAX : 0;  // 初始化 matrix 数组

    cout << "请输入边的信息:\n";
    int u, v, w;
    for (int i = 0; i < edge_num; i++)
    {
        cin >> u >> v >> w;
        matrix[u][v] = matrix[v][u] = w;
    }

    cout << "请输入源点(<" << vertex_num << "):";
    cin >> source;
    Dijkstra(source);

    for (int i = 0; i < vertex_num; i++)
    {
        if (i != source)
        {
            cout << source << " 到 " << i << " 的最短距离是:" << dist[i] << ",路径是:" << i;
            int t = path[i];
            while (t != source)
            {
                cout << "--" << t;
                t = path[t];
            }
            cout << "--" << source << endl;
        }
    }

    return 0;
}

//结果

4.查找

1.顺序表查找

二分法查找,low和high作为待元素的下界和上界,mid指示区间的中间位置。mid=(low+high)/2 向下取整,若mid对应的值大于被查找值key,则high = mid-1,否则low = mid+1,若相等则返回mid。当low<=high时,返回未查找到。

索引顺序表或分块查找

将表分成几个子表,对每个子表建立一个索引项,包含关键字项(子表里面最大的数)和一个指针项。

索引表暗关键字有序,则表有序或块有序

所以最后的查找是分两步进行,先确定待查记录所在的块,然后在块里面顺序查找

2.动态查找表

二叉排序树

动态树表,若左子树不空,则左子树所有节点均小于根节点的值,同理,右子树的节点均大于根节点的值。若查找不成功,就将关键字插入查找路径的访问的最后一个节点的左孩子或右孩子中。

一个无序列表就可通过构造二叉排序树成为一个有序序列。

3.哈希表

使每一个关键字和结构中一个唯一的存储位置相对应。

哈希表有些构造方法,但注意不要产生同义词,产生冲突

1.直接定址法

2.数字分析法

3.平方取中法

4.折叠法

5.随机数法

6.除留余数法 主流

H(key) = key mod p ; p<=m m为表长

核心是对p的取值

一般经验:P可选为质数或不包含小于20的质因数的合数

处理冲突的方法

1.开放定址法

Hi = (H(key) + di) mod m i = 1,2,3…,k (k<=m-1)

H(key)为hash函数,m为hash表长,di为增量序列

di有三种取法

  1. 线性探测再散列 di = 1,2,3…,m-1
  2. 二次探测再散列 di = 1,-1,4,-4,9,…,k^2 (k<= m/2)
  3. 随机探测再散列 di = 随机数

2.再hash法

Hi = RHi (key)

当产生冲突时计算另一个hash函数地址

3.链地址法 主流

将所有关键字为同义词的记录存储在同一线性链表中,凡hash地址为i的记录都插入到线性链表中对应的头节点中,需保持同义词在同一线性链表中按关键字有序。


hash表的查找和hash表的构造过程基本一致

给定K值,根据构造表的hash函数求得hash地址,若表中此位置没有记录,则查找不成功,否则比较关键字,若相等,则查找成功


5.排序

有两个基本操作,1.比较两个关键字的大小。 2.将记录从一个位置移到另一个位置。 一般改变存储方式避免2.

1.插入排序 O(n^2)

将一个记录插入到已排好序的有序表中,从而的到一个新的、记录值+1的有序表

也有一种折半插入排序,优化了查找,结合了之前的二分法查询,进行关键字的判断,进而得到位置 仍是O(n^2)

2.希尔排序

优化了时间复杂度

先将整个待排记录序列分割成若干个子序列分别进行直接插入排序,待记录基本有序之后,再对全体记录进行一次直接插入排序

特点是:子排序的构成不是简单的“逐段分割”,而是将**相隔某个“增量”**的记录组成的一个子序列。增量最好为质数,最后一次排序必须为1,通常为5,3,1

3.快速排序

1.起泡排序 O(n^2)

若为逆序(r[1.key>r[2].key)交换两个记录的位置,然后比较第二个记录和第三个记录关键字,直至n-1和n进行比较过为止。

这算一次起泡排序,结果为最大的记录值被排到了最后一个的位置,之后进行第二次起泡排序,对前n-1个记录进行相同操作

2.快速排序

其是对起泡排序的一个改进

核心思想为通过一次排序将待排记录分割成独立的两部分,其中一部分的关键字均比另一组的关键字小,则可分别对这两部分记录继续排序,以达到整个序列的有序

首先需要选取一个枢轴,一般为第一个记录,之后将比他小的记录移到左边,大的移到右边。这样的一次操作称作一次快排

具体做法为附设两个指针low和high,初值指向low和high,还有一个枢轴记录的关键字pivotkey,之后首先从high的位置向前搜索找到第一个关键字小于pivotkey的记录并和枢轴记录交换位置,之后从low的位置向后搜索找到 一个比privotkey大的记录,并交换位置,重复这两部操作,直至low==high。可使用L[0]存储pivotkey,因为对枢轴的复制是多余的,只需要最后进行移位即可

//快速排序
int Prtition(SqList &L,int low,int high){
    L.r[0] = L.r[low];	//子表的第一个记录做枢轴记录
    privotkey = L.r[low].key;
    while(low<high){	//从表的两端交替向中间扫描
        while(low<high && L.r[high].key>=privotkey)	--high;
            L.r[low] = L.r[high];	//比枢轴记录小的记录移到左边
        while(low<high && L.r[low].key>=privotkey)	++low;
            L.r[high]  = L.r[low];//比枢轴记录大的记录移到右边
    }
    L.r[low] = L.r[0];	//枢轴记录到位
    return low;
}

4.选择排序

1.简单选择排序

通过n-i次关键字比较,选出最小的一个记录,并和第i个记录交换

2.树形选择排序

首先n个关键字进行两两比较,然后再n/2(向上取整)个较小者之间再两两比较,如此重复,直至选出最小的关键字为止

可使用有n个叶子节点的完全二叉树表示,两两比较,最后的即是最小,之后选出次最小关键字,只需将原来的叶子节点里面的最小关键字,即上一次选出来的根的那个关键字,变为无穷大,这样就选出了次最小关键字

3.堆排序

堆排序是对树形选择排序的优化,主要是“筛选”,“建堆”

4归并排序

将两个或以上的有序表组合成一个新的有序表


3.计算机算法思想
1.递归与分治

递归看之前的一些就行,这里主要说分治法,各个击破,分而治之

基本思想是将一个规模为n的问题分解成k个规模较小的子问题,这些子问题相互独立,不包含公共的子问题,且与原问题相同,递归的解这些子问题(将这些子问题继续划分),然后将各个子问题的解合并得到原问题的解。常见的 k = 2

例:二分查找技术,棋盘覆盖,归并排序,快速排序等的具体实现

2.动态规划

适用于解最优化问题,依赖于问题本身的两个核心性质:最优子结构性质和子问题重叠性质。分解的子问题往往不是互相独立的

最优子结构性质:动态规划算法的第一步首先是构造最优解的结构,当问题的最优解包含了其子问题的最优解,称为最优子结构

重叠子问题:递归运算自顶向下解此问题时,每次产生的子问题并不总是新问题,有些种子问题被计算多次,动态规划利用此性质,对每个子问题只解一次,而后将其解保存在一个表格中,当需要再次解此子问题时,只需要查看一下表格。

其每一步的选择都依赖于相关子问题的解,只有相关子问题的解出来后,才进行选择

  1. 找出最优解的性质,并刻画其结构特征
  2. 递归的定义最优值
  3. 以自底向上的方式计算出最优值
  4. 根据计算最优值时得到的信息,构造最优解
  5. 定义动态规划式,给出动态规划递归式,给出非递归代码

例:矩阵连乘,最长公共子序列,最大子段和(分治与动规的区分),租用游艇问题

分治和动规都是自底向上解决问题

//租用游艇问题
int getMinRent(int n)
{
	int dp[200];//令dp[i]为从出租站1到出租站n所需要的最少租金 
	dp[1]=0;//初始化 
	dp[2]=r[1][2];//初始化 
	for(int i=3;i<=n;i++)
	{
		dp[i]=r[1][i];//先初始化dp[i]为第1到i站的租金 
		for(int j=2;j<i;j++)
		{
			dp[i]=min(dp[i],dp[j]+r[j][i]);//状态转移方程 
		}
	}
	return dp[n];//返回n个 
}

3.贪心算法

并不从整体最优上进行考虑,而是在局部最优进行选择,我们希望最后的结果也是整体最优的,虽然一些时候并不知道最优解,但最终结果确实最优解的很好地近似解。通常自顶向下进行求解

贪心每次仅做局部最优解,之后去解做出这个选择后产生的相应的子问题。

贪心算法基本要素:最优子结构性质,

贪心选择性质:所求问题的整体最优解可通过一系列局部最优的选择来达到。而且需要证明每一步做的贪心选择最终导致问题的整体最优解

例:活动安排问题,最优装载,多机调度问题,dijkstra最短路径算法,最小生成树算法


4.回溯法

深度遍历解空间树,从根节点进行遍历,当发现某一节点肯定不包含问题的解,则跳过对其子树的遍历,逐层向祖先节点回溯

通常解题步骤为:首先定义问题的解空间树;确定易于搜索的解空间结构;深度优先搜索解空间,并在搜索过程中使用剪枝函数避免无效搜索

通常使用剪枝函数避免无效搜索:

1.用约束函数咋扩展节点处剪去不满足约束的子树

2.用限界函数减去得不到最优解的子树

一般可使用递归回溯,迭代回溯,子集树与排列树

子集树:从n个元素中找出满足要求的子集

排列树:确定n个元素满足某种性质的排列

图

左边的for循环中间,i<=n

例:0-1背包问题,旅行售货员问题 ,装载问题


5.分支限界法

广度优先+剪枝

一般来说,回溯法是求出问题的所有解;分支限界是求出一个解,或者说是在满足某一约束的最优解

在分支限界法中,活节点一旦成为扩展节点,就一次性生成所有子节点,舍弃所有不可行解或非最优解的儿子节点,其余儿子节点加入活结点表中。

从活节点表中选择下一扩展节点有几种方式

1.先入先出方式

2.优先队列式分支限界法(看哪一个节点先成为扩展节点)

例:装载问题,旅行员售货问题

总之先写到这里了,后面再改吧

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构和简单算法思想 的相关文章

随机推荐

  • vue面试题

    1 vue子组件调用父组件方法 方法1 直接在子组件中通过this parent event来调用父组件的方法 方法2 在子组件里用 emit向父组件触发一个事件 父组件监听这个事件就可以了 方法3 在父组件把方法传入子组件中 在子组件里直
  • JAVA开发运维(关于渗透测试与漏洞修复)

    对于C端的网站 H5 小程序或者app都需要进行渗透测试 渗透测试是模拟真实黑客的攻击手段 对目标网站或主机进行全面的安全评估 与黑客攻击不同 渗透测试的目的是尽可能多地发现安全漏洞 而真正的黑客只需要找到一种入侵 点击进入目标系统 一个好
  • Ubuntu18+ 使用redshift调色温 夜间闪烁

    问题描述 在Ubuntu 18 的系统上 使用redshift色温调节软件时 每到晚上 在切换软件时 还有其他奇怪的场景中 屏幕会有频闪现象 症状看来就像redshift反复开启和关闭 原因与解决方案 原因很可能是Ubuntu 18 的系统
  • chrome浏览器fitler中的XHR作用是什么

    chrome浏览器fitler中的XHR作用是什么 记录ajax中的请求 什么是 AJAX AJAX 异步 JavaScript 和 XML AJAX 是一种用于创建快速动态网页的技术 通过在后台与服务器进行少量数据交换 AJAX 可以使网
  • SpringBoot集成LayuiAdmin的简单使用

    SpringBoot LayuiAdmin的简单使用 分享一下SpringBoot集成LayuiAdmin的一些心得体会 刚开始网上找了半天没找到集成教程 鼓捣了一阵只好自己上手了 快速开始 1 准备一份LayuiAdmin 源码压缩包解压
  • js——修改对象里面的属性名

    代码 var e avatar uploads 20230816 b30044ba6735c83bdea9d43b85c4ae15 jpeg mobile code 111 nickname 小土豆 e aaa e avatar delet
  • Elasticsearch 跨集群复制(CCR)的使用

    什么是 Elasticsearch 的跨集群复制 CCR Cross Cluster Replication 跨集群复制是 Elasticsearch v6 5 发布的一个新的特性 这个特性可以让你将一个集群的索引数据同步复制到远程的另外一
  • 学习太极创客 — MQTT 第二章(八)ESP8266 MQTT 用户密码认证

    视频链接 https www bilibili com video BV1fK4y1L72n spm id from 333 999 0 0 vd source b91967c499b23106586d7aa35af46413 资料链接 h
  • java上传视频文件到服务器,java视频上传到远程服务器

    java视频上传到远程服务器 内容精选 换一换 该步骤必须在root用户下执行 若以普通系统用户登录 需要执行su切换到root用户下执行后续操作 ssh keygen t rsa过程中需要 可选 输入保存的文件名 默认为在 root ss
  • C#学习笔记 委托

    定义委托 有时候可能想要将一个方法传递给另一个方法 在C 中使用函数指针来实现 在JavaScript中由于函数也是对象所以直接可以在参数列表中传递 而在C 中需要使用委托 要使用委托 首先需要定义它 定义一个接受两个int参数 返回一个i
  • 【HDU4741】空间解析几何

    1 题目链接 题目大意 给出两条空间中不平行的直线 求出这两条直线的距离和对应的点 2 分析 在空间中我们知道 直线有三种关系 相交 平行 异面 但是题目中已经说了 是不相交的直线 所以只可能有两种关系 平行或者异面 在空间中 直线方程并不
  • PATH环境变量变化,导致无法找到基本命令

    解决办法 好多命令的位置在 usr bin 恢复办法如下 1 由于找不到sudo 所以必须写全路径 其他命令如果提示找不到 也需要写全路径 usr bin sudo vi etc profile 2 末尾添加以下内容后保存 export P
  • android fwk开发之堡垒机的使用

    在Android堡垒机 Ubuntu 服务器上编译android AOSP源码 1 添加用户 1 切换到root用户 sudo su 2 添加账户 useradd m username 删除用户 userdel r username 使用u
  • 使用elementUI实现el-table表格跨行

    1 概述 element table 有一个属性 span method 可以设置单元格合并 通过给table传入span method方法可以实现合并行或列 方法的参数是一个对象 里面包含当前行row 当前列column 当前行号rowI
  • new bing聊天机器人免翻命令行使用--大佬逆向工程api

    使用 可以看到 IP地址在美国 使用步骤 下载地址 GitHub地址 或者命令行 python3 m pip install EdgeGPT upgrade 获取bing的cookie 不会控制台获取的 可以在edge插件里面下载cooki
  • 爬虫实战之《流浪地球》豆瓣影评分析(一)

    背景与挖掘目标 获取豆瓣评论数据 分析好评与差评的关键信息 分析评论数量及评分与时间的关系 分析评论者的城市分布情况 1 背景与挖掘目标 豆瓣 douban 是一个社区网站 网站由杨勃 网名 阿北 创立于2005年3月6日 该网站以书影音起
  • ChatGPT漫谈(二)

    ChatGPT 脱胎 于OpenAI在2020年发布的GPT 3 任何外行都可以使用GPT 3 在几分钟内提供示例 并获得所需的文本输出 GPT 3被认为是当时最强大的语言模型 但现在 ChatGPT模型似乎更强大 ChatGPT能进行天马
  • VUE 自定义 穿梭框

    某次项目要使用穿梭框进行数据选择 项目使用的element ui框架 框架中的穿梭框是这样子的 好像不能满足我的需求 因为需要展示很多内容 包括图片等信息 也要加上很多样式等等 我尝试这去改造 一会后觉得还是自己动手去写一个靠谱 几经鼓捣效
  • [华为云云服务器评测] 华为云耀云服务器 Java、node环境配置

    系列文章目录 第一章 linux实战 华为云耀云服务器L实例 Java node环境配置 文章目录 系列文章目录 前言 一 任务拆解 二 修改密码 三 配置安全规则 四 远程登录并更新apt 五 安装 配置JDK环境 5 1 安装openj
  • 数据结构和简单算法思想

    只为自己学习进行一下记录 虽然之前上了一些关于数据结构 算法之类的课 但之前都没有怎么搞懂 尤其是算法里面的一些算法思想 现在看能不能补上 就是一些大佬的算法指导 刷LeetCode的一些题 回看之前的书上面的重点 教材是清华大学出版社的数