线性表-静态链表
用数组来实现的链式结构,数组的元素可以看成是一个节点,包括data和next。
注:
- 插入元素,和单链表修改元素类似,都是需要修改被插入元素的next和上一个元素的next。只是静态链表不需要移动元素,删除元素同理,不需要移动元素。
- 这里游标区,是指向了下一个元素,在数组中表现为下标的形式。(因为可以通过数组下标来找到下一个元素,也可以是真实的地址,虽然一般不常用,但会考)
双向链表
相比原来的单链表多了一个指向前一个节点的指针prior
prior | data | next |
---|
typedef struct DNode //定义双链表结点类型
{
ElemType data;
struct DNode* prior; //指向前驱结点
struct DNode* next; //指向后继结点
} DLinkNode;
建表
void CreateListF(DLinkNode*& L, ElemType a[], int n)
//头插法建双链表
{
DLinkNode* s;
L = (DLinkNode*)malloc(sizeof(DLinkNode)); //创建头结点
L->prior = L->next = NULL;
for (int i = 0; i < n; i++)
{
s = (DLinkNode*)malloc(sizeof(DLinkNode));//创建新结点
s->data = a[i];
s->next = L->next; //将结点s插在原开始结点之前,头结点之后
if (L->next != NULL) L->next->prior = s;
L->next = s; s->prior = L;
}
}
void CreateListR(DLinkNode*& L, ElemType a[], int n)
//尾插法建双链表
{
DLinkNode* s, * r;
L = (DLinkNode*)malloc(sizeof(DLinkNode)); //创建头结点
L->prior = L->next = NULL;
r = L; //r始终指向终端结点,开始时指向头结点
for (int i = 0; i < n; i++)
{
s = (DLinkNode*)malloc(sizeof(DLinkNode));//创建新结点
s->data = a[i];
r->next = s; s->prior = r; //将结点s插入结点r之后
r = s;
}
r->next = NULL; //尾结点next域置为NULL
}
插入节点:
bool ListInsert(DLinkNode*& L, int i, ElemType e)
{
int j = 0;
DLinkNode* p = L, * s;
if (i <= 0) return false; //i错误返回假
while (j < i - 1 && p != NULL)
{
j++;
p = p->next;
}
if (p == NULL) //未找到第i-1个结点
return false;
else //找到第i-1个结点p
{
s = (DLinkNode*)malloc(sizeof(DLinkNode)); //创建新结点s
s->data = e;
s->next = p->next; //将结点s插入到结点p之后
if (p->next != NULL)
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
}
- 我想要插入到第五个位置上,就需要连接在第四个元素的后面,第四个元素称之为结点p
- 先节点赋值,然后先连接好等待插入节点的next,然后再去节点的prior,为什么呢?因为如果你先把p节点的next给了指向了s,那后面的节点你就找不到了,无法操作。
删除结点:
bool ListDelete(DLinkNode*& L, int i, ElemType& e)
{
int j = 0;
DLinkNode* p = L, * q; //q是要删除的节点,p是它前面的一个节点
if (i <= 0) return false; //i错误返回假
while (j < i - 1 && p != NULL)
{
j++;
p = p->next;
}
if (p == NULL) //未找到第i-1个结点
return false;
else //找到第i-1个结点p
{
q = p->next; //q指向要删除的结点
if (q == NULL)
return false; //不存在第i个结点
e = q->data;
p->next = q->next; //从单链表中删除*q结点
if (p->next != NULL) p->next->prior = p; //如果p的next为空就没prior
free(q); //释放q结点
return true;
}
}
注:
- 删除节点的话,两句重要的操作语句前后顺序可以是任意的
- 但是对于增加节点来说,就尤其需要注意,防止断链找不到节点的问题
相邻节点交换
设有一个循环双向链表,有一个节点指针为p,编写一个函数使得p与其右边的节点进行交换
void swap(DLinkNode* L, int m) {
DLinkNode* p = L;
while (m--) {
p = p->next;
if (p == NULL) {
return;
}
}
DLinkNode* q = p->next;
p->next = q->next;
q->next->prior = p;
p->prior->next = q;
q->prior = p->prior;
p->prior = q;
q->next = p;
}
循环链表
和普通的单链表相比:
- 没有空指针域(没有指针时指向空的)
- 判断是否为尾节点的标志的是p->next==L (p->next == L->next)
- 如果是循环的双向链表,则能更快的找到尾节点
- 注意最后一个节点的下一个节点是头节点还是第一个节点,不同的题目设定不同
- 在实际使用的过程中,仅含有尾指针的循环链表会更加便利,因为可以通过尾指针来操作尾部的元素,也能快速定位到头节点(第一个结点)。但如果是单向循环链表,想要删除最后的一个节点,仍然需要去找到最后一个节点的前一个节点,这一点对于单向链表仅有尾指针时是非常费时间的。
总结:
线性表的难点还是插入和删除操作,数组实现的顺序线性表增删需要移动元素,链表实现的线性表增删的指针域的变化,头插法尾查法的具体步骤和实现,及其其中的细节。总体内容不多,但常考。