• 程序员修炼之路-(2)线性表(上):数组与链表


    两块基石

    数组与链表构成各种数据结构的基石,是实现所有数据结构必不可少的元素。

    1.1 数组

    数组一般内置于编程语言中,直接通过索引(index)读写。索引一般为数字,有的语言甚至直接支持如字符串等其他类型的索引。在很多数据结构中都能看到数组的身影,例如字符串、动态数组、堆、栈和队列(用链表也可以,但用数组实现很高效)等。

    1.2 链表

    概念上都能理解,但实现起来还真有很多容易出错的地方。

    实现细节

    Ø  表头(header:为什么要添加一个表头?因为有了表头,在第一个结点前添加结点或删除第一结点时会很方便。否则就需要修改客户端持有的链表指针的值,这就要求insert()delete都要传入二级指针,才能修改客户端持有指针的值。

    Ø  插入设计:删除结点时一般是指定要删除结点的value,插入则有三种设计:1追加(append)到链表末尾;2插入(insert到指定位置,通过索引或结点的值来指定位置;3插入(insert到指定位置,但位置是通过指针来指定。其实,后两种方式差不多,区别就在于是insert()自己调用find()还是客户端先调用一下find()查找,再调用insert()

    Ø  删除结点:双向链表就不说了,对于单向链表,删除结点时要遍历至要删除结点的前一个,将这个结点的next域跨过要删除的结点,直接指向要删除结点的next,再释放掉要删除结点占用的内存空间就算完成了。

    Ø  查找结点:结点值匹配时一定要“==”啊!

    C实现

    接口定义:结构Node为内部实现,暴露给客户端的是ListPosition两个指针。函数包括三块:创建和销毁、增删查、调试打印。由于C语言的限制,元素都只支持int。关于命名,所有函数都使用数据结构名List作为前缀,避免客户端使用时发生命名冲突。


    Node结构体:结构体很简单,就是指向下一个结点的指针域next和值域value


    创建和销毁:创建时注意对malloc返回值的判断。销毁时则要注意两点:1逐个结点销毁,最后销毁头结点2使用二级指针:修改客户端持有的List指针的值,避免客户端引用到销毁后的非法内存空间。


    增删查:具体注意点在前面已经都提到过,再次强调一下:1)插入的三种设计,这里实现了1追加和3插入到指针指定的位置两种方式;2)对于单向链表,删除时要遍历到前继结点而不是要删除的那个结点;3)查找比较值时一定要“==”


    C++实现

    模板类定义:得益于C++的类封装和模板功能,代码变得更加结构化,终于回到了我们熟悉的面向对象世界。因为delete是关键字,所以删除方法改名为remove


    Node:结点也封装为一个类Node2,这样为每个结点分配内存、初始化成员变量的工作就都统一放到Node2中了,从后面的List2的构造函数中就能明显看到这种优点。


    构造和析构:因为Node2类的封装,构造函数变得很简洁。而析构函数则没什么改变,只是free()函数调用变成了delete


    增删查:这次插入和删除该用索引下标的设计方式,所以插入和删除都要遍历到前继结点。


    注意wild野指针问题:如果Node2next忘记初始化为NULL,那么后续的append()等各种遍历操作都会访问到一个非法的内存位置,导致程序意外终止退出。


    Visual Studio中调试方法为:异常退出时VS会提示是否调试,选。控制台输出为非法访问内存位置0x000000043,从链表的各个结点的next值能明显看出,前几个结点的next值为0x006ae开头的内存位置,但最后一个结点的内存位置正是控制台输出的,明显与程序分配内存地址差很远,所以这是野指针导致的,查看代码是否有未赋值NULL的指针即可。

    STL实现

    (待补充《STL源码剖析》)

     

  • 相关阅读:
    正则表达式分组小记
    Python中关于try...finally的一些疑问
    hello,world!
    02操控奴隶——掌握它的语言“Python”
    01操控奴隶——奴隶的构成与运行方式
    vue特殊属性 key ref slot
    vue内置组件 transition 和 keep-alive 使用
    vue文档全局api笔记2
    vue文档全局api笔记1
    vue 二三倍图适配,1像素边框
  • 原文地址:https://www.cnblogs.com/xiaomaohai/p/6157633.html
Copyright © 2020-2023  润新知