这里对常见链表这一概念的提出主要是考虑到链表有以下类型,如果每次学习时都需要重新去写,那未免太过要求高了,所以在这里提出。
1、是否有头结点head
2、是否是双向链表prev和next,这使得其难度有了一些增加,但这个的增加反而不复杂,毕竟,本质上的操作是一样的
3、将1和2进行组合,老实讲,这有一些难度了,我认为甚至可以说十分困难也不为过
4、循环链表,老实说难得不在于他的思路,而在于他的细节,每次思考都要相半天,十分繁琐,希望找找看有没有好的方法。
易混概念辨析
首先我们得知道,有一个东西叫头指针,它指向的是链表中第一个元素,这与指针的指向这样的说法不谋而合。
然后我们可以有头节点,节点不是一个箭头,而是一个节点,而有无头结点的区别就是有没有第一个不储存元素且不被当作节点使用的节点(这个就叫头结点)。
然后我们可以对头指针进行一些变换。然后我们将第一个存储数据的节点叫做首元节点。这样关系就可以了。而头指针指向的是第一个节点(而非首元节点)。这可能是因为首元的元是element,即第一个当作element的元素。
没有头指针的一个缺点是我们需要判断头指针是否为NULL,这样导致了操作本身的割裂。
操作分析
下面对一些关键操作进行介绍
首先是理解为什么typedef,因为使用了typedef后,我们可以实现重命名,如果不typedef,如果我们想要使用LNode就要使用struct LNode关键词,而使用了typedef,可以省去struct,而且我们还可以定义struct LNode * LinkList,从而使得我们定义一个头指针LinkList,这个指针就是一个头指针了。
然后我们可以看一下节点。注意头指针是我们的head,由于有了head,二者的判空条件不同,前者的判空是head == NULL,而后者的判空则是head->next == NULL;
头插尾插
链表的插入可以有多个位置,一种是头插,一种是尾插,还有一种是中间插入,其中选择了头插尾插只是因为他们更具有代表性,这个代表性表现在,他们可以在不给出具体索引(如果是中间插入需要给出索引位置k)的前提下,进行插入,所以十分的方便。
双向链表
当我们使用了双向链表时,会有哪些变化呢?
1、我们可以从尾到头的遍历了,但是有什么不同呢,这个时候我们需要一个辅助的变量tail,使用这个名叫tail的链表指针,我们可以指向最后一个节点,这样就可以从尾巴到头遍历,只因为此时我们就有了一个指向前方的链表prev,这个链表很好的对我们提供了帮助。这就是双向链表给我们提供的帮助,即我们可以利用双向链表改变链表使用的方向了!
队列
队列与其他东西不同,就像线性表包括顺序表和链表,队列也是可以通过顺序表和链表实现均可以的。其中队列会有一些不一样的东西。
有何不同
队列的出和入的方向不一样,是先进先出,所以需要两个指针,rear和front,因此他需要两个指针指向两个位置,从而保证出入的操作。而栈的入出在一个口,所以他只需要一个指针top就可以了。
特殊情况
我们如果讨论队列,那么一般队列确实是十分平常的,但是有一种队列却不同,那就是循环队列,就如同循环链表,(当然,一般的循环栈不存在,因为他只有一个口),循环队列也是存在的,不同之点在于,链表的循环是通过将链表的指针相连,而队列也是这样,因此使用循环队列,是一定会有很多细节需要考虑的。
一个圈和一个线会有哪些不同之处呢?
(错误想法)第一个是判断条件,如果是一条线,我们只能判断,是不是rear - front == maxsize,一旦超过了我们就不能再写入数据了,会变得不幸,因为这只是一个线,所以判断条件很简单。而且可以写入的结果也是不一定的。
当然了实际上并不是这样的,因为对于队列而说,它既可以出队,又可以入队,那么难道关键是他们的差吗?不是,关键是这个最后可入队的地方,是不是已经到头了。这个队就这么长,你只能往后排,但是,前边的人不会前移,只有办完事,走了,第一个的牌子往后走罢了,整个队伍是不能增加的哦。所以关键不是人数差,而是最后一个人的位置。这是一个只能由最后一个人位置决定,因为这个队是一个数组,他的大小已经订好了,所以最后一个人的位置也是定好的!
但是如果我们是一个圆的话,我们就还可以往里边写入,这就是他的区别。只不过我们得判断再写入的时候是否覆盖了,我们在判断的时候要取余,我们的判满条件是front == rear + 1 % maxsize,因为我们的front始终是比rear小的哦,所以我们才是使用减1取余的方法取得结果的哦。
什么是判空,我们的front一直弹出数据,知道我们的数据都消失了,这是因为我们的front是会动的,所以我们的front是可以一直弹出的。
因为我们的front如果已经和rear相等了,那么才判满的话,那么满和空就一样了,所以我们选择始终留出一个空闲位,来对队满和队空进行区分,即1号位是不留的。也因此,我们的入队操作是rear = (rear+1)%maxsize。换句话说,我们如果不这样,它满的时候和空的时候是一个状态哦。即由于这两个是一个状态,所以使用它的前一个状态机进行判断是较好的选择。
即front不动,rear动,所以rear大于front,故而要取余。
入队时由于rear已经有数据了,所以总是rear+1位放新数据。
注:以上写法的前提是(rear总是指向写入数据的后一位,因此我们才保留了一个多的状态,以便这种事情不会经常发生)
而且只能是加,因为如果是减的话,可能直接就越界了!
有一个约定俗称?那就是无论是哪个指针,指向的数据块应该是有数据的,而不是有数据的那个的前一个数据块(看一看链表)不是的,这个是可以设计的,但是队列中使用后者。即使这样,有头节点那个的头结点这是没有索引,如果有索引,想来也会被设计为-1.()
初始化为-1,但是判断如果rear==-1,那么赋值front为0
初始化为0,其实相当于让0当头节点。
原本那个,front和rear都是当前有元素的那个快
但是现在这个,front是有元素的那个快的下一个元素。