一、前言
程序员在编程实战操作面前有两个拦路虎,第一个是用递归的思想去解决问题,第二个是数据结构与算法的应用。对于递归,由于其神奇的薄面纱总是然我们觉得难以理解,而大多数递归解法还是需要承担一定的计算负担的,因此我觉得能理解其思想与用法固然好,但是实在无法理解也只能慢慢适应。而对于第二个难题,数据结构与算法基本上每一个程序员都需要了解,因为,算法是解答一个问题的基本逻辑,如果你可以证明这个逻辑是正确的,那接下来的问题就是应该采用什么样的数据结构来保证你的程序能有最高的执行效率,即比较好的时间复杂性,同样内存空间消耗也需要尽可能小。
二、线性表
作为了解数据结构与算法的基础,线性表通常是第一个拿来作为介绍与研究的。但到底线性表的定义是什么呢?很多资料上通常不是直接介绍线性表,而是直接介绍链表、栈、队列这几个数据结构,那到底链表、栈、队列与线性表有没有关系呢?或者说它们是什么关系呢?
首先,什么是线性表?就是一种连续或间断存储的数组,这里的连续和间断是针对物理内存空间中线性表元素之间是否连续,其中连续数组对应内置数组的实现方式,间断数组对应的是指针的实现方式,这种方式也称为链表实现。
也就是说,线性表有两种实现方式,一种是内置数组实现,另一种是链表实现,最好把链表就理解为线性表的一种实现方式,而且从链表的定义来看,它的本质是一种数据结构,其定义如下
struct ListNode
{
ElementType value; //节点值
ListNode *next; //指向下一个节点
};
而我们通常自定义的链表中,一般使用typedef ListNode* List; 这其实就是一个ListNode节点的next域,正是由于有next域的存在,所以我们通常会以一种逻辑结构理解的链表(大多数数据结构教程中确实也是这么表示的)。
三、栈
首先,栈就是一种线性结构的表,也就是线性表,那么也就是说它有两种实现方式,一种是用内置数组实现,一种是以链表实现。
其次,栈有自己的结构特性:栈可以动态增长和缩减,即(一般)可以向一个栈添加或从一个栈删除元素,但这种添加和删除操作只能从栈的栈顶进行操作,这种限制也造就了栈的先进后出特性。
最后,注意上面只是说一般我们可以用内置数组和链表两种方式来实现栈,但是,根据栈的特性,其实还可以用其他结构来实现栈,只要这种结构能实现栈的先进后出,而且只能从栈的栈顶进行插入和删除操作,最常见的就是我们可以用两个队列来实现栈(当然,后面会解释,其实队列也是用数组和链表来实现的)。
比如,用内置数组来实现栈,则可以这样定义栈的结构:
struct stack_array
{
int TopofStack; //表示栈顶
int capacity; //表示栈的容量(固定值)
Elemtype *arr; //指向数组首元素的指针,capacity和*arr两个参数就表示了数组
};
栈的链表实现:
struct stack_list
{
ElemType value;
stack_list *next;
};
如果采用链表方式来实现栈,则一般需要有一个表头,因为栈需要在表头位置来进行插入和删除操作。
三、队列
首先,队列也是一种线性表(线性的数据结构),则队列也可以用数组和链表两种方式来实现;
其次,队列也有自己的结构特性:队列是先进先出的线性表,它同时维护表的两端,但只能在表尾进行插入,在表头进行删除操作(这是有队列的先进先出特性决定的)。
所以,不管是用数组还是用链表实现队列,都需要遵循队列的先进先出特性。
用内置数组实现队列,则队列结构定义为:
struct queue_array
{
int capacity; //队列最大容量
int size; //队列中当前元素个数
ElemType *arr; //数组
int front; //队列头
int rear; //队列尾
};
用链表实现队列,则队列结构定义为:
struct node
{
ElemType value;
node *next;
};
struct queue_list
{
node *front; //队列头节点
node *rear; //队列尾节点
int size; //队列中当前元素个数
};
不管是队列的数组还是链表实现,在进行删除和插入操作之后都需要维护队列头和尾的指向性元素(即front和real),以及队列当前元素个数size。