为了弥补和克服上一节所述顺序存储结构所带来的的不足,这一节讨论线性表的另外一种存储结构——链式存储结构。链式存储结构不要求逻辑上相邻的数据元素在物理位置上也相邻,经过 指针 来映射数据元素之间的逻辑关系。node
使用连式存储结构时,每一个数据元素除了存储自身的数据信息外,还须要存储一个指示其直接后继元素位置的信息,这两部分组成一个 链结点。面试
当链表中每个链结点除了数据域外只设置一个指针域时,称这样的链表为 线性链表 或 单链表。算法
一个链结点定义以下:函数
typedef struct node {
ElemType data;
struct node *link;
} LNode, *LinkList;
复制代码
线性链表的基本操做都比较简单,挑几个重点说明以下。ui
书中没有指定每一个数据元素的来源,这里假设是由用户输入的,使用 scanf
函数。spa
/** * 1. 创建一个线性链表 */
LinkList create(int n) {
LinkList list = NULL, rear = NULL;
for (int i = 0; i < n; i++) {
int val;
scanf("%d", &val);
LNode *node = malloc(sizeof(LNode));
node -> data = val;
node -> link = NULL;
if (list == NULL) {
list = node;
} else {
rear -> link = node;
}
rear = node;
}
return list;
}
复制代码
须要注意一下若是使用的是判断 rear == NULL
,那么 rear
在声明的时候必须赋初值为 NULL
,不然 rear
初始化时是一个垃圾值,不会进入判断条件,使得下面调用 rear -> link
会抛出异常。指针
插入分了三种,往头部插入、往尾部插入以及在某个结点后插入。后面两种比较简单,但注意第一种。C 语言是 按值传递,若是此处函数签名不是 LinkList
的指针(LinkList*
),而直接使用 LinkList
,那么在最后赋值时,list = newNode
是不会起做用的,由于修改的只是 形参的指针。code
/** * 5. 在非空线性链表第一个链结点前插入一个 item * * 注意:签名必须是指针的指针 */
void insertLink1(LinkList *list, ElemType item) {
LinkList newNode = malloc(sizeof(LNode));
newNode -> data = item;
newNode -> link = *list;
*list = newNode;
}
/** * 6. 在非空线性链表的末尾插入一个 item */
void insertLink2(LinkList list, ElemType item) {
LinkList rear = list;
while (rear -> link != NULL) {
rear = rear -> link;
}
LinkList newNode = malloc(sizeof(LNode));
newNode -> data = item;
newNode -> link = NULL;
rear -> link = newNode;
}
/** * 7. 在线性链表指针 p 后面插入一个 item */
void insertLink3(LinkList list, LinkList q, ElemType item) {
LinkList newNode = malloc(sizeof(LNode));
newNode -> data = item;
if (list == NULL) {
newNode -> link = NULL;
list = newNode;
} else {
newNode -> link = q -> link;
q -> link = newNode;
}
}
复制代码
线性链表的反转是一个比较经典的面试题,实际上实现起来也比较简单,主要须要理清思路。blog
实际上须要维护的指针有三个:
cur
)prev
)prevPrev
)当每一步进行反转时,实际上反转的是将 prev
指向 prevPrev
,由于 cur
指针的 link
是不能动的,不然你就找不到下一个链结点了。
因此思路是(while
循环中的四行代码):
prevPrev
)日后走prev
)日后走cur
)日后走prev
与 prevPrev
/** * 13. 线性链表的反转 */
void invert(LinkList *list) {
LinkList cur = *list, prev = NULL, prevPrev = NULL;
while (cur != NULL) {
prevPrev = prev;
prev = cur;
cur = cur -> link;
prev -> link = prevPrev;
}
*list = prev;
}
复制代码
由于链表是单向的,没法直接从后往前寻找倒数第 N 个节点。要使时间复杂度为 O(n)
,即一次遍历实现,能够基于以下思路:
使用两个指针,其中一个指针比另外一个领先 N 个节点,而后两个节点同时日后移动,当前面的指针到达结尾时,后面的指针则到达了倒数第 N 个位置。
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
struct ListNode* pioneer = head;
struct ListNode* prev = head;
for (int i = 0; i < n; i++) {
pioneer = pioneer -> next;
}
if (pioneer == NULL) {
prev = prev -> next;
return prev;
}
while (pioneer -> next != NULL) {
prev = prev -> next;
pioneer = pioneer -> next;
}
prev -> next = prev -> next -> next;
return head;
}
复制代码