c语言游标使用案例,深入显出数据结构C语言版(6)——游标数组及其实现

深入浅出数据结构C语言版(6)——游标数组及其实现

在前两次博文中,我们由表讲到数组,然后又由数组的缺陷提出了指针式链表(即http://www.cnblogs.com/mm93/p/6576765.html中讲解的带有next指针的链表)。但是指针式链表也不是完美无缺的,在某些没有指针数据类型的编程语言中,指针式链表是无法由我们来实现的,但是有时候我们又希望能用上链表,因为链表可以快速的进行插入和删除(见http://www.cnblogs.com/mm93/p/6576765.html)。这个时候我们就可以使用一种由数组来实现的“链表”——游标数组。其优点是可以快速的插入和删除(由于不需要指针式链表的malloc和free等内存操作,所以使用时游标数组可能会比指针式链表还要快一点),缺点则是继承自数组——固定大小。

综合考虑来说,在C语言中如果要使用链表,我们一般优先考虑指针式,主要原因是链表的游标实现的固定大小不容忽视,而指针式虽然插入删除略慢于游标数组,但整体上更具有适应性。那我们为什么还要讨论游标数组呢?原因有两个,第一是我觉得游标数组这种“精妙”的思想值得我写一篇博文;第二是我觉得如果能理解游标数组的思想,那么对链表的理解就肯定到位了;第三是我个人认为指针式链表的诞生初衷是数组的固定大小带来的弊端,而不是为了实现更快的插入删除而出现的指针式链表(有些人认为指针式链表的诞生初衷就是想快速插入删除,能够动态变化大小只是次要的“副作用”)。而游标数组支持我的观点的一个例子,它可以快速插入删除(比指针式更快,因为没有内存操作),却不能动态变化大小。

好了,接下来我们就开始讨论游标数组的想法及实现吧。

首先我们要明白的一点就是:游标数组的“基础”是指针式链表,目的在于通过数组来模拟“链表”。那么,为了达到模拟或者说“山寨”的目的,我们自然要分析指针式链表的关键点有哪些,即分析出哪些点或概念是我们必须模拟出的,然后分析该如何模拟出那些点或概念。

那么,根据“设计链表的过程”(见http://www.cnblogs.com/mm93/p/6574912.html),我们可以发现,链表实现中很重要的一点就是“每个结点都正确记住了下一个结点的位置”(广义上说,数组型表的元素也“记住了”下一个元素的位置,即自身位置+1,但如果删除元素时我们不移动其后面的元素,则原front元素所“记住的”下一元素位置就是错的,我们希望的是“灵活”地记住正确的位置,而不是“死板”地记住+1),再进一步对这一点分析,很容易发现,正是这一点使得我们的插入、删除变得非常快速。当我们插入某个结点时,需要改变的仅仅是front结点的next以及新结点的next(令它们指向新的正确的“下一结点位置”),而当我们删除结点时,我们只需要改变front结点的next(同理,令它指向新的正确的“下一结点位置”)。

既然这一点是实现快速插入与删除的关键,那我们模拟时显然不能丢下,因此我们先记下这一必须模拟的点,即:

1.每个结点都正确记住了下一个结点的位置。

知道了我们需要模拟的这一点后,我们就可以开始试着动手了。首先来画画图,看看基本的实现概念是怎样的(从删除操作入手):

这是一个普通的数组型表:

2240291373.jpg

如果我们删除了原元素2却不将后面的元素向前移,则会出错:

2240291374.jpg

想要解决这个错误很简单,令元素1添加一个指向元素2的“指针”变成“结点1”即可:

2240291375.jpg

显而易见的,我们遇上了一个新的小问题,那就是在数组中,我们又该如何让“结点1记住结点2的位置呢”?现在我们是假定没有指针可用的,所以这个问题看起来很麻烦,但稍加思考就可以发现,数组中的元素都是“自带地址”的,那就是它们的下标!于是我们的结点定义很快就出来了,那就是:

structnode

{char data; //我们之后都将假定元素类型为char以便与next更好的区分

size_t next; //指向下一元素下标

};

结点的定义解决了,接下来摆在眼前的问题是,我们这个由数组型表“改”过来的“链表”该怎么对待大小问题呢?毕竟这可是数组型表的巨大缺陷之一呢!可是正如我们前面所说的:游标数组继承了数组的缺点——固定大小。所以对于这个问题,我们的做法就是“不作为”,不仅如此,我们还要将这个数组定义得很大很大。一方面是防止表的大小超过了数组的极限,另一方面则是我们之后才说的——将数组当做链表专用内存。我们暂且忽略第二点,只认为我们将数组定义的很大是为了防止表的大小超过数组极限。

#define SIZE 1000typedef size_t Position;//为了方便形象,我们设定类型别名

typedef structnode Node;

Node CursorSpace[SIZE];

接下来让我们先把目光放到插入操作上。假设我们执行插入到表尾的操作,我们就会发现下一个问题,那就是“表尾在哪”?在指针式链表中,这个问题很好解决,只要遍历(遍历就是指将所有结点访问一次)链表直到某个结点的next==NULL时该结点就是最后一个结点。数组型表中也很好解决,只要用一个size_t记录下表中有多少个元素即可,因为数组型表是“连贯”的,总共N个元素则表尾就在下标为N-1处。那么,游标数组该如何解决这个问题呢?其实也很简单,类似于指针式链表尾元素next==NULL,我们只要令表尾结点的next指向下标0就好了。但这又带来一个小疑问,如果表尾指向0,那么CursorSpace[0].next又该是什么呢?嗯,这个问题将引出我们之前所说的“将数组当做链表专用内存”的讨论。

既然表尾的next指向0,那么CursorSpace[0]显然不能再作为表中的元素,看起来我们似乎又浪费了数组中的一个位置(之前我们简略讨论了删除操作的实现原理,但我们并没有说被删除的结点如何“回收”,因此我们暂时认为被删除的结点是被永久抛弃了)。然而实际上,我们可以利用这个“废弃”的CursorSpace[0]来做一些更有意义的事情,甚至可以说是因为我们把CursorSpace[0]拿去做更有意义的事情了,才使得它得以空闲出来做表尾next的“NULL”。

那么,CursorSpace[0]要做的更有意义的事情是什么呢?当然是一个很重要的、我们还没有解决的事情喽!回顾插入操作,我们会发现,链表中的插入操作第一步是“分配新结点”,而我们的游标数组中虽然有大量的空位置,但我们还没有用来找到空位置的办法呢!(我们必须得准确地找到空位置,不然将表中的某个结点当做空位置分配给新结点可不是什么好事)现在,我们有办法了,就是让CursorSpace[0].next来告诉我们哪儿有空位置(通过它,我们可以实现“在数组中malloc”一般的功能)!嗯,不错,看起来很棒,但如果我们继续“推进”,很快就会发现下一个问题,当CursorSpace[0].next所指的那个空位置被用掉了,该如何“更新”CursorSpace[0].next使它再次指向一个空位置?这个问题看似麻烦,其实简单,只要让被用掉的原空位置在“临死”前(最近英雄电影看多了……)告诉我们另一个空位置就好了。也就是说,CursorSpace[0]知道一个空位置,而那个空位置又知道另一个空位置,另一个空位置又知道另另一个空位置,以此类推。这样一来,我们就将空位置们“链接”起来了~要做到这一点很也很简单,只要这样的一个初始化程序就好了:

void Init(intN)

{for (int i = 0;i < N-1;++i)

CursorSpace[i].next= i + 1;

CursorSpace[N- 1].next = 0;

}

不难理解,初始化后的游标数组中只有负责malloc的[0]和空位置,CursorSpace[0]指向了第一个空位置[1],而[1]又指向了[2],以此类推,最终所有空位置都指向了下一个空位置,且不会有两个空位置同时指向一个空位置,[SIZE-1]指向0表示其是最后一个空位置。

接下来,让我们“手动追踪”一下数组的操作变化(红色为表头,金色为表中元素,黑色为空位置,蓝色为被删除结点,暂不回收)

初始化后,从CursorSpace[0].next到CursorSpace[n-1].next是这样的:

1,2,3,4,5,6,7,8,9,10……SIZE-1

假设我们插入一个元素,那么根据CursorSpace[0].next,开辟的结点是CursorSpace[1],CursorSpace[0].next变为CursorSpace[1].next以指向新的空位置:

2,0,3,4,5,6,7,8,9,10……SIZE-1

我们继续插入一个元素,则:

3,2,0,4,5,6,7,8,9,10……SIZE-1

删除掉表中第一个元素,则:

3,X,0,4,5,6,7,8,9,10……SIZE-1

再插入,则:

4,X,3,0,5,6,7,8,9,10……SIZE-1

实话实说,我觉得上面这个“手动追踪”例子可能会让人更直观地感受一次游标数组的操作,也可能会让人更迷糊,但如果认真走一遍“手动追踪”应该是可以达到前者目的的╮(╯_╰)╭

讲到这儿,差不多也该稍稍总结一下游标数组的特点了,不然估计得出现每一步都懂但最后就是不会的尴尬局面……

游标数组(尚未考虑删除结点的回收)的特点有这么几个:

1.数组中下标为0的CursorSpace[0]保存着一个空位置的下标(下标即游标数组中的“地址”),它可以给我们起到“malloc”的功能

2.我们在程序中定义一个Position类型的head(或别的名字,都行,类似于指针式链表中程序所存储的那个List变量)作为我们“链表”的表头,其值为表的第一个结点在数组中的下标(通过特点1来分配出一个结点应该是很容易理解的)

3.从表中第一个结点开始,表中的每一个结点的next都保存着表中下一个结点的下标,除了表尾,表尾结点的next==0

4.为了实现1,数组中的空位置的next均指向“下一个”空位置的下标,除了“最后一个空位置”,“最后一个空位置”的next为0

5.“最后一个空位置”初始化时为CursorSpace[SIZE-1]即数组的最后一个位置

6.当分配掉CursorSpace[0].next所指的位置时,令CursorSpace[0].next=分配掉的结点.next(此处写“分配掉的结点”是为了逻辑简单易懂,真实代码应为CursorSpace[0].next=CursorSpace[CursorSpace[0].next].next)

7.显然地,当分配掉最后一个结点时,CursorSpace[0].next=分配掉的结点.next会令CursorSpace[0].next==0,所以当我们发现CursorSpace[0].next==0时我们认为数组(链表专用内存)已满,“malloc”失败

再次回顾,可以发现,我们先是通过“下标”模拟“指针”从而确定了游标数组的结点形式,也就解决了查找、插入、删除最核心的问题;然后,我们通过利用CursorSpace[0]及其next和所有空结点的next,解决了确定表尾的方法(表中结点next==0的即表尾结点)和模拟“malloc”的方法(CursorSpace[0].next及所有空结点的next),而有了模拟“malloc”的方法,自然而然地,我们就找到了实现插入的方法。

#include

#define SIZE 1000

structnode

{char data; //使用char作为元素类型以方便区分next

size_t next;

};

typedef size_t Position;

typedefstructnode Node;

typedef Position Head;//新增的类型别名,用于在程序中保存表头下标

Node CursorSpace[SIZE];//初始化全局“内存”,但我们不显式调用它,初始化会由Create()完成

void Init(intN)

{for (int i = 0;i < N-1;++i)

CursorSpace[i].next= i + 1;

CursorSpace[N- 1].next = 0;

}//用于创建一个新的链表,起到类似于malloc的功能,同时初始化分配到的“头结点”

Head Create(charElem)

{//静态变量,用于判断是否已初始化“内存”

static bool IsInit = false;if (!IsInit)

{

Init(SIZE);

IsInit= true;

}//若CursorSpace[0].next==0则说明“内存”中已没有空位置可分配

if (CursorSpace[0].next == 0)return -1;//反之则说明“内存”中尚有位置

else{

Head result;//用于返回,作用相当于“指针”

result = CursorSpace[0].next; //CursorSpace[0].next总是指向一个“空位置”或0,但此时必然不是0

CursorSpace[0].next = CursorSpace[result].next; //令CursorSpace[0].next指向下一个“空位置”或0(若分配的位置是最后一个空位置)

CursorSpace[result].data= Elem; //令新分配的“结点”的数据域保存新元素

CursorSpace[result].next = 0; //令新分配“结点”的next为0表示其是表中最后一个结点

return result; //相当于返回指向新结点的“指针”

}

}//查找函数,用于返回第N位置上的结点(默认Head中至少有一个结点)

Node Find(Head Head, int N, bool *success)

{//先用局部变量保存下“头结点”位置

Position temp =Head;if (N <= 0)

{

(*success) = false;return CursorSpace[0]; //返回值是“随意的”,调用者需要自己对success进行判断来确定是否成功

}//稍微注意一下循环次数

for (int i = 1;i < N;++i)

{//若出现CursorSpace[temp].next==0的情况,即未到第N位置却已到表尾,说明N越界

if (CursorSpace[temp].next == 0)

{

(*success) = false;return CursorSpace[0];

}

temp= CursorSpace[temp].next; //类似于指针式链表

}

(*success) = true;returnCursorSpace[temp];

}//插入函数,接收Head*因为可能要修改Head指向的位置(插入到第1个位置时),N为要插入的位置,Elem为要插入的元素

bool Insert(Head* Head, int N ,charElem)

{//简单地判断是否非法

if ((*Head) <= 0)return false;//若CursorSpace[0].next==0则说明“内存”中已没有空位

if (CursorSpace[0].next == 0)return false;//分配一个空位置并初始化(没有修改next因为时候未到),令CursorSpace[0].next保存下一个空位置(一个空位置的next指向另一个空位置或0)

Position newIndex = CursorSpace[0].next;

CursorSpace[0].next =CursorSpace[newIndex].next;

CursorSpace[newIndex].data=Elem;//若希望的插入位置为1,则特殊对待(需要利用Head*)

if (N == 1)

{

CursorSpace[newIndex].next= (*Head); //注意我们现在修改了新结点的next

(*Head) =newIndex;

}//若N<0则插入到表末尾

else if (N <= 0)

{

Position temp= (*Head);for (;CursorSpace[temp].next != 0;++temp);

CursorSpace[temp].next=newIndex;

CursorSpace[newIndex].next= 0;

}//其他情况下我们将结点插入到第N位置上

else{

Position temp= (*Head);//注意循环次数,我们停在了第N-1个结点处

for (int i = 1;i < N - 1;++i)

{//若还没到N-1处就到了表的末尾,则出错

if (CursorSpace[temp].next == 0)

{return false;

}

temp=CursorSpace[temp].next;

}//此处类似指针式链表操作

CursorSpace[newIndex].next =CursorSpace[temp].next;

CursorSpace[temp].next=newIndex;

}return true;

}

在我们开始讨论删除操作之前,我们先给出一个简单的遍历输出函数:

voidprintCursor(Head h)

{

Position temp=h;for(int i = 0;i < SIZE; ++i)

{if (CursorSpace[temp].next == 0)

{

printf("%c\n", CursorSpace[temp].data);return;

}

printf("%c", CursorSpace[temp].data);

temp=CursorSpace[temp].next;

}

}

好了,现在我们可以来讨论讨论删除操作了。

乍一看,Delete应该是一项很简单的工作,只要令CursorSpace[front].next=rear就行了。在指针式链表中,我们必须记得free()来释放结点所占的位置,但在游标数组中这一操作似乎变得可有可无,毕竟我们并没有真的调用过malloc()。但是稍加思考我们就会发现,如果我们没有模拟free()操作,那么其带来的影响和指针式链表Delete时未free()是类似的,都会造成“内存占用浪费”,如果我们在游标数组中Delete时没有实现free(也就是我们之前一直说的“回收”),那么我们的链表其实最多只能累积Insert()SIZE次(即使你delete过某些结点,它们也被永久废弃,而整个数组就只有SIZE大小)。

因此,我们的游标数组必然需要考虑如何模拟free(),否则Delete带来的浪费实在太大了。

其实模拟free()在游标数组中的关键点就是:怎么让被delete的结点插入到“空位置组成的那个链表”里。可能这么说有点绕口,再简单点说就是:怎么让要回收的结点可以被CursorSpace[0].next指到?之前被delete的结点会永久废弃就是因为不论是CursorSpace[0].next还是其它空位置,没有谁的next是指向要回收的结点的,我们要改变的就是这一点,就是要让CursorSpace[0]或其它空结点知道某个结点又“回来了”。

其实解决的方法简单得不行,CursorSpace[0].next不是指向一个空位置吗?那我们就让它指向我们回收的这个“新”空位置(即discard,丢弃的表中结点):CursorSpace[0].next=discard;那原先的空位置们怎么办?简单啊,在CursorSpace[0].next=discard;之前,先CursorSpace[discard].next=CursorSpace[0].next不就行了?

很快,我们的Delete函数就出炉了:

bool Delete(Head * Head, intN)

{//简单地判断是否非法

if ((*Head) <= 0)return false;

Position temp= (*Head);//默认N<0时删除尾结点

if (N <= 0)

{//说明只有一个结点

if (CursorSpace[temp].next == 0)

{

CursorSpace[temp].next= CursorSpace[0].next;

CursorSpace[0].next =temp;

(*Head) = 0;return true;

}else{//注意循环条件,我们在倒数第二个结点处停下

while (CursorSpace[CursorSpace[temp].next].next != 0)

temp=CursorSpace[temp].next;

Position discard=CursorSpace[temp].next;

CursorSpace[temp].next= CursorSpace[discard].next; //CursorSpace[discard].next就等于0

CursorSpace[discard].next = CursorSpace[0].next;

CursorSpace[0].next =discard;return true;

}

}else if (N == 1) //删除第一个结点

{

(*Head) =CursorSpace[temp].next;

CursorSpace[temp].next= CursorSpace[0].next;

CursorSpace[0].next =temp;return true;

}else if (N > 1)

{//注意循环次数,我们停在第N-1个结点处

for (int i = 1;i < N - 1;++i)

{//若出现CursorSpace[temp].next==0的情况说明链表大小小于N

if (CursorSpace[temp].next == 0)return false;

temp=CursorSpace[temp].next;

}

Position discard=CursorSpace[temp].next;

CursorSpace[temp].next=CursorSpace[discard].next;

CursorSpace[discard].next= CursorSpace[0].next;

CursorSpace[0].next =discard;return true;

}return false;

}

Oh,Yeah!看起来一切都万事大吉了!我们利用数组的下标,模拟了内存的地址;利用数组的一个位置(CursorSpace[0]),我们模拟出了malloc;通过一点小智慧,我们实现了free也即回收;通过这些模拟,我们完成了游标数组!

但是等一等,我们貌似还是没说出“链表专用内存”是个什么玩意儿……哈哈,其实这个东西没什么高大上的!你想想看,我们几乎模拟出了整套链表的内存操作:地址、malloc、free,那这整个数组是不是就相当于一个“专属内存”?一个只能存储指定结点的,专用于链表的“内存”?并且!既然是一个“内存”,就说明它可以支持不仅仅一个链表在其中!这才是我们真正强调“内存”的意义所在哦~

稍加思考就可以发现,其实我们只要不断通过Create()得到一个“头结点”,然后在Insert()和Delete()时给定“头结点”,我们就可以在一个游标数组中保存多个结点结构相同的“链表”!!

是不是瞬间明白了为什么我们说要尽可能的把数组定义得大一点的原因呢?O(∩_∩)O

好了,这下我们是真的把游标数组讲完了orz     希望这篇文章做到了将游标数组浅显易懂的讲解的目的呢~

—————————————————————————————————————————————————————————————

写这篇博文花了挺长时间,期间一直试图将知识点拆散成合适的讲解顺序,并且将各个关键点都讲得浅显易懂,但过程中却经常出现“越想讲简单,就越容易讲详细,越讲详细就越容易讲复杂”的怪圈,希望哪里讲解的不好不对的,能够有人提醒我一下,写完已是深夜,所以结尾略草率,但粗看一遍觉得还是不难的,因此就此打住,发布!

—————————————————————————————————————————————————————————————

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/53839.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为谷歌android 6.0是什么,盘点那些用上Android 6.0的手机

目前市面上大多数智能手机运行的都是Android系统&#xff0c;这些年来Android系统也是在不断地升级中。终于在今年的9月30日&#xff0c;广大用户迎来了Android 6.0版本&#xff0c;这个代号为棉花糖的新系统在UI设计和系统交互方面做出了很大的提升。 不过&#xff0c;大家也都…

测试自动化面试题

Python python有哪些数据类型? Python支持多种数据类型&#xff0c;包括以下常见的数据类型&#xff1a; 数字类型&#xff1a;整数&#xff08;int&#xff09;、浮点数&#xff08;float&#xff09;和复数&#xff08;complex&#xff09;。 字符串类型&#xff1a;由字…

视频自动生成字幕VideoSrt

软件介绍 这是一个可以识别视频语音自动生成字幕SRT文件的开源软件工具。适用于快速、批量的为媒体&#xff08;视频/音频&#xff09;生成中/英文字幕、文本文件的业务场景。 软件截图 GitHub https://github.com/wxbool/video-srt-windows

Video Caption(跨模态视频摘要/字幕生成)

Video Caption 视频摘要/视频字母生成属于多模态学习下的一个子任务&#xff0c;大体目标就是根据视频内容给出一句或多句文字描述。所生成的caption可用于后续的视频检索等等&#xff0c;也可以直接帮助智能体或者有视觉障碍的人理解现实情况。 典型的架构如上图&#xff08;图…

视频批量添加滚动字幕,我1分钟就搞定了

最近有很多朋友在问&#xff0c;如何剪辑视频&#xff0c;比如给多段视频添加滚动字幕&#xff0c;该如何实现呢&#xff0c;下面随小编一起来试试&#xff0c;希望能给大家带来帮助。 材料准备&#xff1a; 一台Win系统的电脑 视频素材若干 步骤演示&#xff1a; 打开【媒体…

如何根据音频转文字自动给视频加字幕

2020年2月更新&#xff1a;目前这款软件已经变成收费软件&#xff0c;须知&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 可以考…

android访问服务器405,Http 405 错误

因为现在都用的spring配置的&#xff0c;很少用到servlet了&#xff0c;今天想写一个demo,结果报了一个405的错误.....................真的是打扰了....... 405概念 请求行中指定的请求方法不能被用于请求相应的资源。该响应必须返回一个Allow 头信息用以表示出当前资源能够接…

让数字人出圈的技术秘籍,华为率先公开了

明敏 萧箫 发自 凹非寺量子位 | 公众号 QbitAI 在刚刚结束的2022华为开发者大会&#xff08;HDC2022&#xff09;上&#xff0c;升级版的手语数字人再次亮相&#xff0c;为大会的主题演讲进行了实时翻译。 相较去年HDC&#xff0c;手语数字人不仅在形象上有了优化&#xff0c;覆…

马斯克组织Code Review,并晒出推特架构图?其代码能力被低估了

近日&#xff0c;马斯克前往 Twitter 总部大楼和工程师团队进行了 code review&#xff0c;并在结束后晒出了合照&#xff0c;以及 Twitter 的系统架构图。 当然这不是 Twitter 的整体架构图&#xff0c;主要是展示了 Timeline 部分的架构&#xff0c;其中关键的组件是 Timelin…

组织变革方法论:华为从IBM得到的关键启示

1998年&#xff0c;华为在IBM顾问帮助下启动了长达十年&#xff0c;以IPD为先导的组织变革&#xff0c;这是当时IBM给到华为的变革管理方法论。今天读来&#xff0c;对中国企业的组织变革依然具有很强的参考价值。 01 业务变革的背景 90年代初&#xff0c;新系统观及高速发展的…

向98年的华为学习:没有高管办公室的青铜器软件

1995~1998华为走上快车道&#xff0c;98年华为的销售额为89亿人民币&#xff0c;员工7000人&#xff0c;公司所有副总裁级别的人都没有独立办公室&#xff0c;也只是在偌大办公室后排&#xff0c;拥有一张简陋的办工桌而已。 如下文章摘抄自&#xff1a;《军工文化》 因为笔者…

华为干部分类赋能手册(珍藏)

战略决定人才配置&#xff0c;战略转型和业务发展意味着人才能力升级&#xff0c;人才是支撑战略实现的第一要素。 通常在不同的时代背景下和企业发展阶段&#xff0c;人力资源都会被赋予不同的使命和责任&#xff0c;在新的时代背景下&#xff0c;组织和个体的关系也发生了根本…

华为宣布成功实现MetaERP研发和替换

华为宣布实现自主可控的MetaERP研发&#xff0c;并完成对旧ERP系统的替换。为了表彰在此项目中作出重大贡献的相关团队和个人&#xff0c;华为在东莞溪流背坡村园区举办了“英雄强渡大渡河”MetaERP表彰会。 曾在二级市场引起轩然大波的国产ERP系统&#xff0c;又一次引起市场关…

[转帖]华为变革史(下)

华为变革史&#xff08;下&#xff09; https://www.huxiu.com/article/300116.html 本文来自微信公众号&#xff1a;华夏基石e洞察&#xff08;ID&#xff1a;chnstonewx&#xff09;&#xff0c;作者&#xff1a;苗兆光、施炜&#xff0c;头图来源&#xff1a;东方IC 接上文&…

WhatsApp营销之群组(二):搜群

新建群就是客户提供联系人和管理员&#xff0c;给群名、群头像、系统商用小号创建群组&#xff0c;按照客户要求来进行创建群组的服务。 对于新建群组&#xff0c;如果打完广告就走&#xff0c;这样群组的威力完全没有发挥出来&#xff0c;未免失去了建群的意义。 根据很多客…

华为干部管理经典模型

“副职一定至少要精于管理&#xff0c;大大咧咧的人&#xff0c;不适合做副职。副职一定通过精细化管理&#xff0c;来实施组织意图。” “正职必须要敢于进攻&#xff0c;文质彬彬、温良恭俭让、事无巨细、眉毛胡子一把抓&#xff0c;而且越抓越细的人是不适合做正职的。” …

Linux下history查看历史操作记录,并显示操作时间

一、在查看历史的操作记录有两种方式。 1.在用户的目录下的.bash_history文件中 [rootlocalhost ~]# cat ~/.bash_history vi /etc/sysconfig/network-scripts/ifcfg-eth0 setup service netwok restart service network restart vi /etc/hosts vi /etc/sysconfig/network …

生成式AI(Generative AI)将重新定义生产力

文章大纲 人工智能模型的新范式“生成式AI模型(Generative Model)”GPT 模型的演进历史生成式AI(Generative AI)将重新定义生产力编写代码金融行业信息安全芯片领域参考文献与学习路径人工智能模型的新范式“生成式AI模型(Generative Model)” 决策式AI模型- Discriminan…

AI做PPT,五分钟搞定别人一天的量,最喜欢卷PPT了

用AI做PPT 主题生成大纲制作PPT 主题生成大纲 如何使用人工智能工具&#xff0c;如ChatGPT和mindshow&#xff0c;快速生成PPT。 gpt国内版 制作PPT&#xff0c;你可能只有一个主题&#xff0c;但没有明确的提纲或思路。 问gpt&#xff1a;计算机视觉的周工作汇报。我这周学…

MNIST手写数字识别数据集研究意义及分析

1 研究MNIST数据集对于本人课题的意义 本人的硕士研究课题是缺陷检测&#xff0c;缺陷检测也是机器学习&深度学习算法在图像处理中的应用&#xff0c;它的难点在于算法创新。因此&#xff0c;在正式开始进行缺陷检测算法的研究之前使用MNIST数据集对于经常用到的图像处理算…