在解答PTA1015的时候,花费了较多的时间尝试了一下单向链表的创建和排序,结果是令人失望的,因为自身经验的不足,导致前期错误出现的非常多,这时候就体现出链表不方便的地方了——调试不友好。在花费一晚时间调试错误之后,又出现了众多奇怪的问题,诸如超时、答案错误等等,仔细观察了一下,对于小量数据,好像是没有错误,但是一旦面临大规模数据,就力不从心了,特此总结。
下面我们从几个问题入手,对产生的问题简单地进行剖析,希望自己能长个教训,避免日后工作和研究过程中出现此类问题,也顺便复习下大学内学到的关于链表的知识。
- 链表的数据结构及C语言实现
- 使用链表的好处与坏处
- 如何调试链表
- 使用链表的性能问题
1、在C语言中,链表的表示可以借助结构体(Struct)来表示节点,一个典型的节点数据结构如下:
struct ListNode{
int a;
int b;
struct ListNode* Next;
}Node;
其中除了包含必要的数据项外,还包含一个指向下一节点的指针。通过各节点串联,可以形成一个简单的链表。
需要注意的是,链表作为一种线性表,但不支持随即访问,即按下标访问。访问尾部的元素需要遍历该元素之前的所有元素,在某些方面将会显著降低效率。这也是接下来要讲的。
2、链表的好处与坏处
使用链表可以满足某些动态的扩展和删减,譬如在Node1与Node2之间插入一个元素,只需要更改Node1的指针即可,而如果使用数组,那么还需要做一个连续后移操作,复杂度也就上去了。当然,坏处也是显而易见,除了以上说的不支持随即访问外,还存在着运行状况难以监管的情况,尤其是对新手不友好,指针的大量使用容易造成一些未知错误,且不容易调试。
3、关于调试链表的一点经验
- 调试链表最好能够在遍历链表时做一个打印工作,连同节点地址一同打印,这样可以尽早发现一些越界操作。指针越界是一个大坑,遍历打印调试是一个好方法。由于链表动态增长特性,调试越界错误与数据规模无关,也就是可以通过少量数据发现这种错误,除非内存堆栈被填满。
- 调试链表时也可以配合数组进行,通过创建一个辅助数组,记录链表处理过程的结果,这样就可以不需要打印,在调试中直接通过窗口监测,会使输出界面比较干净。
- 链表在处理插入操作时容易产生断链、循环、跳跃等错误,即链表前后断开、链表出现不该出现的环路、链表几个节点脱离链表。对于这些错误也是建议通过遍历链表,尽早发现,否则会影响之后的所有操作。
4、关于性能问题
以下是本人在处理PTA1015过程中使用的两种算法比较:
当然以上只是本人测试结果,没有普遍意义,但是对于大家来说也说明了一点,某些意义上自己造轮子不一定有官方库好用。四五倍的运行时间差距说明了一切。作者的BubbleSort操作为交换两个节点的指针,其中至少有6条语句,交换前指针和后指针。如果是交换数据,则至少有9条语句,因为有三个变量。我想运行时间如此之长也跟这些操作有关。