Teknologian jakaminen

[Tietorakenne] Kaksoislinkitetty luettelo

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Hei~! Tämä on kamppaileva pieni lammas Olen ylpeä siitä, että voit lukea artikkelini. Tervetuloa!
💥💥个人主页:Taisteleva lammas
💥💥所属专栏:C-kieli

🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。


Esipuhe

Yksittäin linkitetty lista voi käyttää tietoja vain yhteen suuntaan tietyn solmun osoitteen kautta, kun taas kaksoislinkitetty lista voi käyttää tietoja molempiin suuntiin solmun osoitteen kautta. Lisäyksen, poistamisen, tarkistamisen ja muokkaamisen tehokkuus on suurempi mutta koodin toteutus on yksinkertaisempaa kuin yksitellen linkitetty luettelo.


1. Linkitettyjen luetteloiden luokittelu

Linkitetyt luettelot on jaettu kahdeksaan tyyppiin: otsikko, ei-otsikko, yksisuuntainen, kaksisuuntainen, syklinen ja ei-syklinen. Opimme vain yleiset.Yksisuuntainen ei-syklinen linkitetty luettelo ilman johtajaajaKaksisuuntainen pyöreä linkitetty luettelo, tuo onYksittäinen listajaKaksoislinkitetty lista
Kaksoislinkitetty lista:
Lisää kuvan kuvaus tähän
Itse asiassa edellisessä artikkelissa kutsuimme yksitellen linkitetyn luettelon ensimmäistä solmua pääsolmuksi, mikä on epätarkka. Syy miksi sitä kutsutaan niin, on helpompi ymmärtää, koska yksitellen linkitetyssä luettelossa ei ole päätä, ja vain kaksoislinkitetyssä luettelossa on pääsolmuSentinellin asema

  • Kaksoislinkitetyn luettelon pääsolmuun ei ole tallennettu kelvollisia tietoja, mutta virheellisiä tietoja.
  • Sentinel-asemaa ei voi muuttaa, vain sen suuntaa voidaan muuttaa.

2. Kaksoislinkitetyn luettelon käyttöönotto

2.1 Kaksinkertaisesti linkitetyt luettelosolmut

Kaksinkertaisesti linkitetyn luettelon solmujen on tallennettava tiedot sekä edellisen ja seuraavan solmun osoitteet. Siksi kaksoislinkitetyn luettelon solmut ovat:

typedef int dlist_data_type;

//双向链表节点
typedef struct dlist
{
	dlist_data_type data;
	struct dlist* next
	struct dlist* prev;
}dlist;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.2 Hae solmua

Toisin kuin yksitellen linkitetyssä luettelossa, kaksoislinkitetyssä luettelossa on oltava vähintään yksi solmu, joka on pääsolmu, jota kutsutaan myös vartijapaikaksi.
Ennen kuin lisäät, poistat, tarkistat tai muokkaat,Kaksoislinkitetyn listan on alustava vartijabitti, ja sentinelbitti tallentaa virheellistä dataa.

Sovelletulla solmulla on aluksi kaksi itseensä osoittavaa osoitinta.

//申请节点
dlist* dlist_buy(dlist_data_type x)
{
	dlist* newdlist = (dlist*)malloc(sizeof(dlist));
	if (newdlist == NULL)
	{
		perror("malloc fail!");
		return 1;
	}
	newdlist->data = x;
	newdlist->next = newdlist;
	newdlist->prev = newdlist;
	return newdlist;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

On kaksi tapaa alustaa vartijabitti:
Palautettu parametreina:

void dlist_init(dlist** pphead)
{
	assert(pphead);
	*pphead = dlist_buy(-1);
}
  • 1
  • 2
  • 3
  • 4
  • 5

Tämä menetelmä edellyttää toissijaisten osoittimien käyttöä.
Palautettu palautusarvona:

dlist* dlist_init()
{
	dlist* phead = dlist_buy(-1);
	return phead;
}
  • 1
  • 2
  • 3
  • 4
  • 5

Tämä menetelmä on yksinkertaisempi ja helpompi ymmärtää.


2.3 Tietojen tulostus ja haku

Tietojen tulostaminen ja haku eivät eroa paljoakaan yksittäislinkin listan vastaavista, joten en mene yksityiskohtiin.
Ainoa asia, joka vaatii huomiota, on ehto silmukan lopettamiselle. Silmukka päättyy, kun osoitin osoittaa vartiobittiä, eikä kun osoitin osoittaa vartiobittiä.NULL

//打印数据
void dlist_print(dlist* phead)
{
	assert(phead);
	dlist* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULLn");
}
//查找
dlist* dlist_find(dlist* phead, dlist_data_type x)
{
	assert(phead);
	dlist* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

2.4 Päätulppa ja takatulppa

Yksittäin linkitetystä listasta poiketen kaksoislinkitetyn listan lisäys-, poisto-, kysely- ja muokkaustoiminnot eivät muuta sentinel-bittiä, joten vainkutsu arvon mukaanSe siitä.
Kaksinkertaisesti linkitetyn luettelon lisäystoiminto edellyttää kolmen solmun muokkausta Muokkausprosessin aikanaMuokkaa ensin uutta solmua, sitten seuraavaa solmua ja lopuksi muokkaa edellistä solmua.

//插入操作不改变哨兵位,因此传一级指针即可
//尾插
void dlist_push_back(dlist* phead, dlist_data_type x)
{
	assert(phead);
	dlist* newdlist = dlist_buy(x);

	//新尾节点
	newdlist->prev = phead->prev;
	newdlist->next = phead;

	//旧尾节点
	phead->prev->next = newdlist;

	//头节点(哨兵位)
	phead->prev = newdlist;
}
//头插
void dlist_push_front(dlist* phead, dlist_data_type x)
{
	assert(phead);
	dlist* newdlist = dlist_buy(x);

	//新节点
	newdlist->prev = phead;
	newdlist->next = phead->next;

	//旧节点
	phead->next->prev = newdlist;

	//哨兵位
	phead->next = newdlist;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

2.5 Pään ja hännän poistaminen

Poistotoimenpiteen edellytyksenä on, että solmuja ei voi olla (Sentinel-asemaa ei lasketa), tallenna silti solmun osoite ennen poistamista, liity uudelleen kaksoislinkitettyyn luetteloon ja vapauta sitten solmu.

//尾删
void dlist_pop_back(dlist* phead)
{
	assert(phead);
	assert(phead->next != phead);//双向链表不能为空
	dlist* del = phead->prev;
	//新尾节点
	del->prev->next = phead;
	//哨兵位
	phead->prev = del->prev;
	free(del);
	del = NULL;
}

//头删
void dlist_pop_front(dlist* phead)
{
	assert(phead);
	assert(phead->next != phead);
	dlist* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2.6 Lisää ja poista tietoja määritetyssä paikassa

//在指定位置之后插入数据
void dlist_insert_back(dlist* pos, dlist_data_type x)
{
	assert(pos);
	dlist* newdlist = dlist_buy(x);
	newdlist->prev = pos;
	newdlist->next = pos->next;

	pos->next->prev = newdlist;
	pos->next = newdlist;
}
//删除指定位置的节点
void dlist_erase(dlist* pos)
{
	assert(pos);
	dlist* del = pos;
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Kun tiedot on poistettu määritetystä sijainnista,Koska tätä funktiota kutsutaan arvon perusteella, määritämme vain muodollisen parametrin osoittimen määritetyssä paikassa olevan solmun vapauttamisen jälkeenNULL, ja varsinainen parametriosoitin osoittaa edelleen tähän paikkaan, joten funktiokutsun päätyttyä varsinainen parametriosoitin on asetettavaNULL


2.7 Tuhoa kaksoislinkitetty luettelo

Kaksinkertaisesti linkitetyn listan tuhoamisen loppuehto on irtautua silmukasta, kun läpikulkuosoitin osoittaa pääsolmuun.Lopuksi vartija-asento on vapautettava erikseen, kun kaksoislinkitetyn listan tuhoamisfunktiokutsu on valmis, on myös asetettava vartijabitille osoittava osoitin.NULL

//销毁
void dlist_destroy(dlist* phead)
{
	assert(phead);
	dlist* pcur = phead->next;
	while (pcur != phead)
	{
		dlist* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(pcur);
	pcur = NULL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3. Kaksoislinkitetyn luettelon täydellinen koodi

dlist.h:

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int dlist_data_type;

//双向链表节点
typedef struct dlist
{
	dlist_data_type data;
	struct dlist* next;
	struct dlist* prev;
}dlist;

//初始化
//插入数据之前,双向链表必须初始化到只有一个头节点(哨兵位)
//void dlist_init(dlist** pphead);//以参数的形式返回
dlist* dlist_init();//以返回值的形式返回

//打印数据
void dlist_print(dlist* phead);
//查找
dlist* dlist_find(dlist* phead, dlist_data_type x);
//尾插
void dlist_push_back(dlist* phead, dlist_data_type x);
//头插
void dlist_push_front(dlist* phead, dlist_data_type x);
//尾删
void dlist_pop_back(dlist* phead);
//头删
void dlist_pop_front(dlist* phead);
//在指定位置之后插入数据
void dlist_insert_back(dlist* pos, dlist_data_type x);
//删除指定位置的节点
void dlist_erase(dlist* pos);
//销毁
void dlist_destroy(dlist* phead);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

dlist.c:

#define  _CRT_SECURE_NO_WARNINGS

#include "dlist.h"

//申请节点
dlist* dlist_buy(dlist_data_type x)
{
	dlist* newdlist = (dlist*)malloc(sizeof(dlist));
	if (newdlist == NULL)
	{
		perror("malloc fail!");
		return 1;
	}
	newdlist->data = x;
	newdlist->next = newdlist;
	newdlist->prev = newdlist;
	return newdlist;
}

//初始化
//void dlist_init(dlist** pphead)
//{
//	assert(pphead);
//	*pphead = dlist_buy(-1);
//}

dlist* dlist_init()
{
	dlist* phead = dlist_buy(-1);
	return phead;
}

//打印数据
void dlist_print(dlist* phead)
{
	assert(phead);
	dlist* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULLn");
}

//查找
dlist* dlist_find(dlist* phead, dlist_data_type x)
{
	assert(phead);
	dlist* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//插入操作不改变哨兵位,因此传一级指针即可
//尾插
void dlist_push_back(dlist* phead, dlist_data_type x)
{
	assert(phead);
	dlist* newdlist = dlist_buy(x);

	//新尾节点
	newdlist->prev = phead->prev;
	newdlist->next = phead;

	//旧尾节点
	phead->prev->next = newdlist;

	//头节点(哨兵位)
	phead->prev = newdlist;
}

//头插
void dlist_push_front(dlist* phead, dlist_data_type x)
{
	assert(phead);
	dlist* newdlist = dlist_buy(x);

	//新节点
	newdlist->prev = phead;
	newdlist->next = phead->next;

	//旧节点
	phead->next->prev = newdlist;

	//哨兵位
	phead->next = newdlist;
}

//尾删
void dlist_pop_back(dlist* phead)
{
	assert(phead);
	assert(phead->next != phead);//双向链表不能为空
	dlist* del = phead->prev;
	//新尾节点
	del->prev->next = phead;
	//哨兵位
	phead->prev = del->prev;
	free(del);
	del = NULL;
}

//头删
void dlist_pop_front(dlist* phead)
{
	assert(phead);
	assert(phead->next != phead);
	dlist* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}

//在指定位置之后插入数据
void dlist_insert_back(dlist* pos, dlist_data_type x)
{
	assert(pos);
	dlist* newdlist = dlist_buy(x);
	newdlist->prev = pos;
	newdlist->next = pos->next;

	pos->next->prev = newdlist;
	pos->next = newdlist;
}

//删除指定位置的节点
void dlist_erase(dlist* pos)
{
	assert(pos);
	dlist* del = pos;
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

//销毁
void dlist_destroy(dlist* phead)
{
	assert(phead);
	dlist* pcur = phead->next;
	while (pcur != phead)
	{
		dlist* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(pcur);
	pcur = NULL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159

test.c:

#define  _CRT_SECURE_NO_WARNINGS

#include "dlist.h"

void test()
{
	//dlist* plist = NULL;
	//dlist_init(&plist);

	dlist* plist = dlist_init();

	//尾插
	dlist_push_back(plist, 1);
	dlist_push_back(plist, 2);
	dlist_push_back(plist, 3);
	dlist_print(plist);
	
	//头插
	//dlist_push_front(plist, 1);
	//dlist_push_front(plist, 2);
	//dlist_push_front(plist, 3);
	//dlist_print(plist);

	//尾删
	//dlist_pop_back(plist);
	//dlist_pop_back(plist);
	//dlist_pop_back(plist);
	//dlist_print(plist);

	//头删
	//dlist_pop_front(plist);
	//dlist_print(plist);
	//dlist_pop_front(plist);
	//dlist_print(plist);
	//dlist_pop_front(plist);
	//dlist_print(plist);

	//dlist* find = dlist_find(plist, 2);
	//dlist_insert_back(find, 4);
	//dlist_print(plist);

	//dlist* find = dlist_find(plist, 2);
	//dlist_erase(find);
	//find = NULL;
	//dlist_print(plist);

	dlist_destroy(plist);
	plist = NULL;//手动置空
}

int main()
{
	test();
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

4. Sekvenssiluettelon ja linkitetyn luettelon vertailu

SekvenssitaulukkoLinkitetty luettelo (kaksinkertainen linkitetty luettelo)
Säilytystilallaloogisesti ja fyysisesti jatkuvaaLoogisesti jatkuva, ei välttämättä fyysisesti jatkuva
satunnainen pääsyMonimutkaisuus O(1)Monimutkaisuus O(N)
Lisää tai poista tietoja missä tahansaTiedot on siirrettävä, monimutkaisuus O(N)Vaihda vain osoitin kohtaan
lisääDynaamista järjestystaulukkoa voidaan laajentaa, kun tilaa ei ole riittävästi.Ei käsitettä kapasiteetista
SovellusskenaariotTehokas tiedon tallennus + toistuva pääsyLisää ja poista tietoja usein missä tahansa
välimuistin käyttökorkeaMatala

Sekvenssiluetteloilla ja linkitetyillä listoilla on toisiaan täydentäviä etuja, ja ne voivat käyttää vastaavia etujaan erilaisissa sovellusskenaarioissa.


Tee yhteenveto

  • Jos sovellusskenaario vaatii toistuvia haku- ja poistotoimintoja ja voi hyväksyä enemmän muistinkulutusta, kaksoislinkitetty luettelo voi olla sopivampi.
  • Jos muisti on rajallinen tai haku- ja poistotoimintojen tehokkuus ei ole korkea, yksittäin linkitetty luettelo voi olla sopivampi.