Felling By Ruiy:
Pre-learnning link list knowloages
熟悉 指针相关操作应用+结构体数据类型应用,且能简单融合使用,堆内存(内存泄露)->类似于你使用完在食堂吃饭的饭盒,吃完饭后你把那个餐具饭盒给带走了,没还给食堂,使此饭盒没能再次被别的人使用.哎,在此Ruiy也只能这般形象的比拟下了,剩下的自己想吧!为了能打出这个类比 我的头都想痛了哈,~_~嘿嘿!,数据溢出(类似于你的水杯就那么大,你从饮水机中放入的水超过了你的水杯的最大容量了,所以水溢出了,你懂的,行政姐也许要骂你了,你浪费水了,公司月开支费用申请不易哈);
栈溢出(产生于函数调用,函数调用时即开辟栈空间,栈也是一种存放数据的空间,空间本身是内存的一部分,内存空间有限,栈资源同样也是有限资源,如果过于频繁的调用新函数而不让以调用的函数运行,将会导致栈溢出(overFlow))
指针?结构体?玩链表,不懂它哥俩,那你洗洗睡吧,你也不用玩了,因为你肯定玩它不转;
指针在链表中主要作用是维护各个结点间关系,结点调度,结构体成员在链表在被称为结构变量(结点),是玩结点的基础,可以说,你玩链表,注意玩的就是这个哥们儿;
链表最怕的就是断链,你的2轱辘座驾->自行车怕的也是这个,链条断了,你就不骑它了,它哥们也许就骑你呀的了!
哥没上大学,理论知识不照,就不多扯了,言辞语句貌似有那么点点不是太通顺,哎凑合着看看吧,Ruiy哥也不想搞的太专业化了,那样不好,Ruiy哥是个比较随和的人,所有你们和Ruiy哥搞神马事情不能太拘谨,不然哥哥就不照了哈,咱俩也就玩不转了;
综述,大家在搞技术前都喜欢搞类比,搞搞现实生活中的生活小案例与相关的技术问题作类比,搞抽象技术性问题成形象化;
此时咱谈的是CPP中的链表!对此你联想到现实生活中的那一幕了?
Ruiy跟着前人的步伐走,隐约能透过迷雾看到类似于自行车链条,但看的还不是那么透彻,对此也没事,来日方长嘛,只怕有心却人心不顾,^_^;
把利用结构指针连接起来的结构变量称为链表(Link list),结构变量称为链表中的结点(NOde);
和数组(存储一系列数据,每项数据的数据类型前提是需要同类型)/结构体数据类型(存储一系列数据,各项数据类型不同)链表也是用来存储一系列数据的,也是计算机总存储数据的最基本的数据结构之一;
有了数组结构体及数组的动态内存分配,那么链表的用武之地在哪里?
玩过即时战略游戏,像魔兽争霸,红色警戒,玩游戏的行家应该不知!每个战斗单位各自的属性计算机室如何分配的?你也许会说,我玩我的,我管这鸟事?
OK!我飘到下一个话题;
部队的数量在程序执行前未知,如果用数组来存储这些数据(且配备动态数组分配),也同样避免不了系列的问题
如:游戏前浪费内存存储空间(没那么多的部队),游泳后期存储空间不够(战斗单位增加)
最合理的内存分配方式->每造一个战斗单位分配内存空间
此时心问题出现了:建造单位的时间不可能完全连续,单位分配的内存空间也不连续,空间不连续就意味着没有了方便的数组下标,
因此很难把零零散散的内存空间集中起来管理;
链表在此种情况登上it历史舞台;
链表可以在程序运行时根据实际需要一个个分配堆内存空间,并且用它的指针把这以系列的空间串联起来,类似于自行车链条,
此时就能够利用指针对整个链表进行管理了;
简单结构体指针应用,
链表实现预知任务
a. 完成创建一个具有若干个结点的链表
b. 能够访问到链表中的每一个结点,即输出每个结点的数据,
c. 能够根据数据查找到结点所在的位置,
d. 能够在链表的任意一位置插入一个结点
e. 能够在链表的任意一个位置删除一个结点
f. 能够在程序结束前清除整个链表,释放内存空间;
概念强记:
热身项,都知链表内存空间是动态分配的,虽然每次只分配一个结构变量(结点),但却少不了指向这个结构变量的指针;
如果任何一个分配给我们的结构变量失去了指向它的指针,那么这个内存的空间将无法释放,也无法操作使用,最终造成内存泄露,由于指针维系着个结点之间关系,指向整个
链表中的某个结点的指针丢失,则就造成了结点间断链,整个链表则被破坏;
因此玩链表我们要保证每个结点都在我们的控制之内,即我们能够通过各种手段,利用指针访问到链表的任意一个结点,
概念必记项,(便于玩转链表,创整个,删增结点,):
由于链表的第一个结点也是动态分配的!因此一个链表始终要有一个指针指向它的表头,不然我们将无法找到这个链表,通常把表头指针称为head;
多结点链表中,新的结点总是连接在原链表的尾部(此时是默认,非在链表的中间某个结点插入一个新的结点),据此你应该明白会意了,链表必须有一个指针始终指向链表的尾部结点,链表的表尾指针称为pEnd;
链表的每个结点都是动态分配的,每分配好一个结点会返回一个指针,因此我们需要一个指针来接受刚分配好的新结点,新创建结点指针称为pS;
在foreach link list,需要有一个 指针能够灵活活动,指针链表中的任意一个结点,以读取个结点的数据,此访问指针被称为pRead;
至此Ruiy简单总结链表操作规则 头指针(head),尾指针(pEnd),新结点指针(New Node),Random Read pointer,不知君以会意否;
小例,将从stdin(标准输入输入的数据保存到链表中,再将链表中的数据显示到)stdout;
此程序还不是一个完整的程序,程序中没有清除此链表的功能?
先跳过;
链表的查询
在对链表进行各种操作时,需要先对某个结点进行查询定位,
链表结点插入
数据在内存中是顺序存储的,那么要在数组中插入一个数据是否就变得颇为麻烦或是无法插入呢?
生活类似场景,一排麻将中随机插入一个牌,则在需要插入的这张的后面部分牌依次顺移,我们知道链表中各结点间是由指针维护的!
在链表中插入一个结点,有点像你我小时候玩就自行车链条?
根据实际生活场景?你在链条(假设你是把一个完整的链条从某一处环节处接一个结点,不是在以确定的环境处,那是因为小时候玩过的是自行车的链条断了后我们再来用一个新的环节点来把整个链条给重新接起来哦),反正大致就这意思;
所以我们预筹备事件为新插入结点;
1,知道被操作链表,表头指针需head知道;
2,为了确定插入位置,插入位置前的结点指针pGuard
3,如果插入的位置是表头,由于操作的是表头而不是一个结点,所以需要特殊处理;
4,通常插入链表结点采用的是"先接再断的方式",即先把需要插入的结点处接上新结点,再把此处断开接上;
删除结点
同新建结点类似,为了保持内存空间链表的顺序存储特性,在删除某个数据时,其后的数据都要依次前移,类似新建结点后,后面的所有结点都要后移一样;
依然我们需要知道我们需要删除那个结点,即需要知道表头指针head;
又知待删除的结点是由结点数据决定的,删除此结点后的事情就是连接去除这个结点后的两个结点连接工作;
由于要对待删除结点作内存释放,需要有一个指针p指向待删除结点
删除结点的过程中,采用的是先连后断的方式,先把待删除结点的前趋结点和它的后续结点连接,再把待删除结点与它的后续结点断开,并释放其空间;
据此总结,不论是新增,还是插入结点,遵循 的都是先连接后断开在连接方式;
void Delete(node * &head,char keyWord)//此函数中的head是引用,因为有可能要用到,那就是当链表中无结点时,操作的就是head啦 { if(head!=NULL)//表示链表中是存在结点的, { node *p;//我们说过删时需要一个指针来存储被删除结点的指针,用于在从链表删除此结点时释放此结点的内存空间, 不然你的内存就泄露啦; node *pGuard=head;//pGuard指针指向待删除结点的前趋结点,待删除需要删除的结点后,会用这个结点与被删除结点的下一个结点进行链重新衔接; if(head->data==keyWord)//此处表示假设头结点数据符号关键字 { p=head;//头结点是待删除结点 head=head->next;//先链,此处是head和head链 delete p; cout<<"The deleted node is"<<keyWord<<endl; return; } else { while(pGuard->next!=NULL)// { if(pGuard->next->data==keyWord) { p=pGuard->next; pGuard->next=p->next;//删除结点前的先接链 delete p; cout<<"The deleted node is"<<keyWord<<endl; return; } pGuard=pGuard->next; } } } cout<<"The keyword node is not found or the link list is empty!" }
清除链表,
清除链表的任务步骤
表态指针head,清除对于的链表
需要一个指针存储待删除结点的指针
类似于删除表头结点的操作,我们任然要遵循先连后断的游戏规则,先把表头指向头结点的后续,再删除头结点;
void destory(node * &head) { node *p; while(head!=NULL) { p=head;//头结点是待删除结点 head=head->next; delete p; } cout<<"The link list has been deleted!"<<endl; }
到此,简单介绍了关于链表的相关操作;
在此简单稍带叙述下链表存储与数组存储(此处是动态的,即堆内存空间heap)各自的优缺点