Graphviz绘制链表

Graphviz绘制链表

写稿目的

本来想偷懒,对于链表的绘制,想在A4纸张上进行绘制,然后拍个照片作为电子资料,然后存储起来,但是考虑到本人字体“捉鸡”,也不想在相册中保存或者将照片上传到某一硬件介质进行存储,也不可能将A4值作为材料保存(因为考虑到纸质资料不能够随身携带,不可能将所有带有笔记的纸质资料都带在身上),所以,通过本篇的学习,来绘制基本的链表,之后便可以将由软件绘制出来的图片集成到支持markdown语法的文档中,这样就完成了一篇比较美观的电子化资料了。

开发环境准备

  • Visual Studio Code

  • 安装Graphviz,因为我的是Windows系统,所以选择Windows版本的安装即可。安装之后需要在系统的环境变量的path路径下,添加dot.exe可执行文件的所在路径。

  • 然后在Visual Studio Code上安装相应的支持库和语言

    在这里插入图片描述

  • 节点可视化准备

    • Ctrl+Shift+P, 在打开的窗口中找到,Graphviz:Open Preview to the Side,点击打开窗口即可,如下

      在这里插入图片描述

代码实例分析

背景介绍

本来我是在学习Android的消息机制时碰到了MessageQueue,一直想知道其内部关于“消息是如何进入队列中”的具体实现,所以去查看了它内部的原理,主要是类MessageQueue中的enqueueMessage函数,经过分析发现其内部是维护了一个链式链表,以新的想加入这个链式表中的消息的when值与链式表中的其他消息的when值进行比较,然后按照规则加入链式表中,具体代码如下:

1  boolean enqueueMessage(Message msg, long when) {
2          if (msg.target == null) {
3              throw new IllegalArgumentException("Message must have a target.");
4          }
5          if (msg.isInUse()) {
6              throw new IllegalStateException(msg + " This message is already in use.");
7          }
8
9          synchronized (this) {
10              if (mQuitting) {
11                  IllegalStateException e = new IllegalStateException(
12                          msg.target + " sending message to a Handler on a dead thread");
13                  Log.w(TAG, e.getMessage(), e);
14                  msg.recycle();
15                  return false;
16              }
17
18              msg.markInUse();
19              msg.when = when;
20              Message p = mMessages;
21              boolean needWake;
22              if (p == null || when == 0 || when < p.when) {
23                  // New head, wake up the event queue if blocked.
24                  msg.next = p;
25                  mMessages = msg;
26                  needWake = mBlocked;
27              } else {
28                  // Inserted within the middle of the queue.  Usually we don't have to wake
29                  // up the event queue unless there is a barrier at the head of the queue
30                  // and the message is the earliest asynchronous message in the queue.
31                  needWake = mBlocked && p.target == null && msg.isAsynchronous();
32                  Message prev;
33                  for (;;) {
34                      prev = p;
35                      p = p.next;
36                      if (p == null || when < p.when) {
37                          break;
38                      }
39                      if (needWake && p.isAsynchronous()) {
40                          needWake = false;
41                      }
42                  }
43                  msg.next = p; // invariant: p == prev.next
44                  prev.next = msg;
45              }
46  
47              // We can assume mPtr != 0 because mQuitting is false.
48              if (needWake) {
49                  nativeWake(mPtr);
50              }
51          }
52          return true;
53}

其中,我关注的是消息是如何进入这个队列中的,所以,看代码的时候可以将一些状态标记和异常检查忽略掉,比如if (msg.target == null)if (msg.isInUse())mQuitting、msg.markInUse()、needWake等,只需要关注line19到line45即可。

  • 注意:mMessages是一个类型为Message的成员变量。

  • 对于Message类,我们目前只需要关注以下内容即可

    public final class Message implements Parcelable{
    	long when;
        Message next;
        // 以下省略其他的成员变量和方法
    }
    

    Graphviz绘制出来如下:

    digraph {
        label = "\n msg";
        rankdir = LR; // 布局从左到右
        node [shape = record]; 
        //list [label = "{<s0> when | <s1> next}"]横向
        list [label = "<s0> when | <s1> next"] //链表元素的排列顺序:纵向
        listnode0 [label = "<s0>null"]; // 声明一个空节点
    
        list:s1 -> listnode0 // 指定节点的指向
    
    }
    
    

在这里插入图片描述

接下来,我们先看enqueueMessage的第一部分,即line22到line27,分三种情况考虑

  • p == null
  • when == 0
  • when < p.when
先考虑三种特殊情况

备注:以下粉红色的节点表示为我们新加入的节点msg

p==null的情况

以下是p==null时执行代码line20到line26的情况。

  • 起初mMessages的节点情况如下:

    在这里插入图片描述

  • 执行函数enqueueMessage中line24之后,情况如下:

    在这里插入图片描述

  • 执行代码line25之后,情况如下:

when == 0 || when < p.when

出现此情况的前提是p == null 这个条件不成立,所以mMessages中至少有一个节点,同时这两种情况都是将待插入节点msg添加到链表头部的情况,所以以下分一个节点和多个节点2中情况进行新节点插入的分析:

  • 初始情况如下,分单个节点和多个节点:

    在这里插入图片描述

    在这里插入图片描述

  • 执行函数enqueueMessage中line24之后,情况如下:

    在这里插入图片描述

    在这里插入图片描述

  • 执行代码line25之后,情况如下:

    在这里插入图片描述

    在这里插入图片描述

再考虑enqueueMessageline32到line44

该情况指的是,mMessages中至少存在一个节点,且待新加入的节点msg不加在链表头部的情况。

根据对“when == 0 || when < p.when”情况的分析,我们知道,单节点和多节点的插入逻辑其实是一样的,所以下面使用多节点的方式对代码进行分析。

for循环中line36到line38是for循环的终止条件,表示待加入节点msg一直和这个链表中的其他节点的when变量进行比较,如果msg节点的when值比链表中的某个节点的when值小活着到链表的结束位置还没在链表中找到一个节点(它的when值比msg节点的when值大或相等),则结束循环。

以下以for循环的第一次执行进行节点图的绘制,其他执行类推即可:

  • prev = p

    在这里插入图片描述

  • p = p.next

    在这里插入图片描述

此时,我们假设待加入节点msg的when的值满足for循环中line36到line38的循环终止条件,则接下来跳出循环,执行line43和line44两行代码,其节点图如下所示

  • msg.next = p

    在这里插入图片描述

  • prev.next = msg

    在这里插入图片描述

参考

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言版本的直观链表创建和读取工具,隐藏了链表的操作细节 可以非常方便地在单片机等平台做FIFO应用,前提是不要用的太嗨,在极小内存的平台上建立好多个FIFO 支持多线程程序,目前尚未发现BUG /*---------------------------- 版本 : V0.01 作者 : ICARUS 日期 : 2019年6月3日 ------------------------------*/ //--------------------------------------------------------------------------------------------// 内置函数 int SL_Create( ) 创建一个数据链表,返回其在系统内的ID值,创建不成功则返回 SL_R_ERR int SL_Delete(int ID) 删除某个使用SL_Create()函数创建过的链表 int SL_InsertInturn(int ID,char* chain,int len,char ** NewNode) 向链表序号为ID的链表尾部插入chain为首地址,长度为len的数据块,数据块在系统中的内存地址将被NewNode返回,若想得到该内存地址,用(*NewNode) 使用示例: char * NewNode; SL_InsetInturn(5,"Hey,boy",strlen("HeyBoy\r\n"),&NewNode;); printf("%s",*NewNode); //------------------------------------------------------------------------------------------// int SL_ReadInturn(int ID,char * chain) 从链表序号为ID的链表头部读取数据到chain中,若返回值大于0,则返回值为数据长度,否则读取失败或链表中已无数据可读。 这一读操作会把数据节点从链表中移除 使用示例: int ReadLen; char chain[1500]; ReadLen = SL_ReadInturn(5,chain); if(ReadLen > 0) { ..... } 注意:由于时间原因,没有来得及修改SL_NodeModify()函数的实现,代码中注释掉的部分请先不要使用,请等待下一版本发布 使用时配合自定义的数据类型效果更佳。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值