二重リンクリスト
1.簡単な紹介
二重リンクリストの意味は、次のノードのアドレスに加えて、ノードが前のノードのアドレスを追加で格納する必要があるということです。
このように、ノードの1つのアドレスがわかっていると仮定すると、単一リンクリストの「後方」トラバーサルだけでなく、「前方」と「後方」を簡単にトラバースできます。
したがって、二重リンクリストは単一リンクリストよりも柔軟性があり、トラバースにかかる時間はわずかに短くなりますが、追加のストレージスペースが必要になります。一部の質問は法外で「前進および後退」する可能性があるため、単一リンクリストではこれらの機能を完了できません。二重リンクリストは、非常に典型的な「時間の空間」です。
2.アイデア図
2.1ノード
ノードを作成するには、prev
data
next
3つの部分が含まれている必要があるため、単一リンクリストと同様である必要があります。prev
前のノードのアドレスをポイントし、next
次のノードのアドレスをポイントします。
2.2リンクリスト構造
二重リンクリストの構造のため、1つだけを格納することはできず头地址
、1つを宣言する必要があり尾地址
ます。順方向トラバーサルの場合、トラバーサルが終了する境界は*next = tail
;逆方向トラバーサルの場合、トラバーサルが終了する境界は*prev = head
です。さらに、スペースを節約するために、カウントを追加できます(リンクリストの長さをカウントします)。index
識別子として挿入するとindex
、リンクリストの長さと比較を通じてトラバース方向を取得できます。
もちろん、面倒な場合は、リンクリストを作成する必要はなくnode
、最初のノードのデータを直接使用して、リンクリストの長さを記録することができます。この記事では、最初の方法を使用します。
2.3リンクリストの初期化
head
*tail
len = 0
3つの部分を含む構造を個別に作成し、初期値を割り当てることをお勧めします。
2.4リンクリストのトラバーサル
正の順序または逆の順序で出力できますが、トラバーサルの「開始」と「プロセスの方向」が異なります。
次の操作で任意のノードのアドレスを取り出して、このコードを再利用できるように、方向を自動的に決定できるトラバーサル関数を作成することをお勧めします。
2.5リンクリストの挿入
二重リンクリストの挿入は非常に簡単です。必要なのは次のことだけです。
- 挿入するノードが
NewNode
される*prev
変更NextNode
を*prev
、*next
変更LastNode
の*next
。 - その後、変更の合計前のノードをするのアドレス挿入されたノード、および設定は完了です!
LastNode
*next
prev
NewNode
2.6リンクリストの削除
その周りの2つのノードを変更する必要があります。
- 変更
LastNode
*next
*next
- それは変更ので、あなたがそれを通過することができないことを、そのノードが削除されました
NextNode
*prev
*prev
2.7リンクリストの変更/クエリ
この部分はトラバーサルと同等であり、値を変更するだけで済みます。
3.コードの実装
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
typedef struct dataType{
int data1;
int data2;
}dataType; // 数据类
typedef struct nodeType{
struct nodeType *prev;
struct nodeType *next;
dataType data;
}nodeType; // 节点类
typedef struct listType{
nodeType *head;
nodeType *tail;
int len;
}listType; // 链表类
dataType DEFAULTDATA={
0,0}; // 创建一个默认的类,作为数据结构
nodeType* NewNode(dataType data);
listType* ListInit(void);
nodeType* ListGetNode(listType *list, int index);
int ListShow(listType *list, int way);
int ListInsert(listType *list, dataType data, int index);
int ListDelete(listType *list, int index);
int ListModify(listType* list, dataType data, int index);
int ListQuery(listType* list, dataType data, int way);
int main()
{
listType* list = ListInit();
dataType test = {
1,2};
//主函数可以自己修改
ListInsert(list, test, 0);
test.data1 = 2;
ListInsert(list, test, 1);
test.data1 = 3;
ListInsert(list,test,2);
ListShow(list, 0);
ListModify(list, test, 0);
ListShow(list, 0);
printf("i find in %d\n", ListQuery(list, test, 0));
printf("i find in %d\n", ListQuery(list, test, 1));
return 0;
}
nodeType* NewNode(dataType data)
{
nodeType* node = (nodeType*)malloc(sizeof(nodeType));
if(node != NULL)
{
node->data = data; // 把数据的值赋值给节点
node->prev = NULL; // 并初始化节点的信息
node->next = NULL;
return node;
}
exit(1);
}
listType* ListInit(void)
{
listType* list = (listType*)malloc(sizeof(listType));
if (list != NULL)
{
list->head = NewNode(DEFAULTDATA); // 创建两个节点给头尾
list->tail = NewNode(DEFAULTDATA);
list->head->next = list->tail; // 并将头的地址指向尾
list->tail->prev = list->head; // 尾的地址指向头
list->len = 0; // 初始化长度
return list;
}
exit(1);
}
nodeType* ListGetNode(listType *list, int index)
{
nodeType *begin, *end;
int cont = 0;
if (list->len == 0)
{
return list->tail; // 如果链表为空,那么就返回尾节点
}
else
{
if (index > list->len / 2) // 通过1/2判断从前还是后遍历
{
begin = list->tail;
end = list->head;
cont = list->len;
// 因为len刚好比index序号大一,所以尾节点的序号刚好等于len
// 反之,如果是头节点,那么序号就是 -1
while(begin != end && cont > index) // 遍历的边界就是链表的末尾,或者cont= index
{
begin = begin->prev;
--cont; // 遍历节点与更新当前index
}
return begin; // 此时的链表节点,就是index指向的节点,或者表头
}
else
{
begin = list->head;
end = list->tail;
cont = -1;
// 头节点的序号就是-1
while(begin != end && cont < index) // 遍历的边界就是链表末尾或者cont=index
{
begin = begin->next;
++cont; // 这部分和上面一样
}
return begin;
}
}
}
int ListShow(listType *list, int way)
{
nodeType* begin;
nodeType* end;
int cont = 0;
if (way == 1) //way=1时,从后往前遍历,否则都是从前开始遍历
{
begin = list->tail->prev;
end = list->head;
cont = list->len - 1;
// 此时的开始节点是尾节点的前一个,要么是有效的,要么就是头节点
// 所以开始的节点序号是len-1
while (begin != end)
{
printf("No.%d is %d %d\n", cont,begin->data.data1, begin->data.data2);
begin = begin->prev;
--cont;
} // 遍历,并输出信息
printf("----------------\n");
return OK;
}
else
{
begin = list->head->next;
end = list->tail;
cont = 0;
// 从头节点的后一个节点开始,所以序号因该是0
while (begin != end)
{
printf("No.%d is %d %d\n", cont,begin->data.data1, begin->data.data2);
begin = begin->next;
++cont;
} // 同理 都是遍历
printf("----------------\n");
return OK;
}
}
int ListInsert(listType *list, dataType data, int index)
{
nodeType *newNode = NewNode(data); // 先创建一个节点
nodeType *nextNode = ListGetNode(list, index); // 获得index节点的地址
nodeType *lastNode = nextNode->prev; // 插入节点前的节点地址
if (nextNode->prev != NULL)
{
newNode->prev = nextNode->prev; // 修改插入节点的信息
newNode->next = nextNode;
lastNode->next = newNode; // 更新插入节点的前节点信息
nextNode->prev = newNode; // 更新插入节点的后节点信息
list->len += 1; // 一定不要忘记把长度加1
return OK;
}
else
{
return ERROR;
}
}
int ListDelete(listType* list, int index)
{
nodeType *dNode;
nodeType *nextNode;
nodeType *lastNode;
if (list->len == 0)
{
return ERROR;
}
else
{
dNode = ListGetNode(list, index); // 找到要删除的节点
if (dNode->next == NULL || dNode->prev == NULL)
{
return ERROR; // 确保不是头节点或者尾节点
}
nextNode = dNode->next; // 更新前后节点的信息
lastNode = dNode->prev;
lastNode->next = nextNode;
nextNode->prev = lastNode;
list->len -= 1; // 一定不要忘记要修改链表的长度
return OK;
}
}
int ListModify(listType* list, dataType data, int index)
{
nodeType *thisNode = ListGetNode(list, index); //查找节点
if (thisNode->next != NULL || thisNode->prev != NULL) // 确保不是首尾节点
{
thisNode->data = data;
return OK;
}
else
{
return ERROR;
}
}
int ListQuery(listType* list, dataType data, int way)
{
nodeType *begin;
nodeType *end;
int cont = 0;
if (way == 0)
{
begin = list->head->next; // 创建开始与结束节点
end = list->tail;
cont = 0; // index计数
while(begin != end)
{
if (begin->data.data1 == data.data1 && begin->data.data2 == data.data2)
{
return cont; //找到了
}
else
{
++cont; // 没找到,更新节点信息
begin = begin->next;
}
}
return -1; // 没找到
}
else
{
begin = list->tail->prev;
end = list->head;
cont = list->len - 1;
while(begin != end)
{
if (begin->data.data1 == data.data1 && begin->data.data2 == data.data2)
{
return cont;
}
else
{
--cont;
begin = begin->prev;
}
}
return -1; // 反之同上,只是顺序变化了
}
}