1. Erste Kenntnisse der verknüpften Liste

Vorschläge hervorbringen

Lesen Sie die Informationen mehrerer Studierender ein und verarbeiten Sie diese. Da die Anzahl der Schüler unbekannt ist, wird dynamisch zugewiesener Speicher zum Speichern von Schülerinformationen verwendet

struct  student{   /*学生信息结构类型*/
       char  nu[7];         /*学号*/
       char  name[9];    /*姓名*/
} ; 

main()
{
      char nu[7];
      struct  student * ptr;
      
      printf("请输入学生学号");
      gets(nu);//读入学号
    
      while(strcmp(nu,"0000")!=0){//判断学号是否合法
          
          ptr = malloc(sizeof(struct  student)) ;
          strcpy(ptr->no,no);//学号复制到内存中 
          printf("请输入学生姓名");
          gets(ptr->name); //读取姓名到内存中
          
          printf("请输入学生学号");
          gets(nu);
      }
      ...
} 

Beteiligte Funktionen: gets(), strcmp(), strcpy.

Bestehendes Problem : Da der Speicher dynamisch zum Speichern von Schülerinformationen zugewiesen wird, ist der Speicher, in dem die einzelnen Schülerinformationen gespeichert werden, normalerweise diskontinuierlich . Wie kann auf diese Schülerinformationen zugegriffen werden?

Lösung 1: Speichern Sie die Speicheradresse der Schülerinformationen im Zeiger-Array.

Es gibt ein Problem: Die Länge des Zeiger-Arrays ist immer noch festgelegt

Bild-20220321124140763

Tragen Sie Fragmente in Ketten auf und verwandeln Sie physikalisch diskontinuierliche Fragmente in logisch kontinuierliche Fragmente

• Wie werden Speicherfragmente verkettet?

• Verwendung von Zeigern – Der Kern von Zeigern besteht darin, Adressen zu speichern

• Nutzen Sie die Struktur als kleinste Einheit der Speicherzuordnung

• Definieren Sie eine Komponente (Mitglied), die Zeiger in der Struktur speichert

• Nutzen Sie den dynamischen Speicherzuweisungsmechanismus, um Strukturspeicher zuzuweisen

selbstreferenzierende Struktur

Eine Variable vom Typ kann struct bookin einer Struktur nicht neu definiert werden.struct book

Es ist jedoch möglich, struct bookZeiger auf Typen zu definieren: struct book*(bekannt als selbstreferenzielle Strukturen ).

Bild-20220321125802932

Eine selbstreferenzierende Struktur enthält ein Zeigermitglied auf eine Struktur desselben Typs wie sie selbst. wie:

struct node{
    
    
    int data;
    struct node* pNext;
}

Dazu gehört struct node*die selbstreferenzielle Struktur.

pNextEs wird als Link (Link) bezeichnet und dient dazu, eine struct nodeStrukturvariable eines Typs mit einer anderen Strukturvariablen desselben Typs zu verbinden.

struct node n1,n2;
n1.pNext=&n2;

Bild-20220321212239332

verlinkte Liste

Struktur einer verknüpften Liste

Schüler Informationen:

typedef struct student{
    
    
    //数据域
    char num[7];//学号
    char name[9];//姓名
    //指针域
    struct student* pNext;//记录下一个
}
Bild-20220321212731950

Wenden Sie 3 Strukturvariablen dynamisch an, um die Informationen von 3 Schülern zu speichern, und die 3 Strukturvariablen werden durch Zeiger mit einer Vorgänger- und Nachfolgerbeziehung miteinander „verkettet“ . Die Adresse der ersten Strukturvariablen wird separat in einem Zeiger festgehalten .

verlinkte Liste

  1. Definition:

    Es handelt sich um eine lineare Sammlung von **selbstreferenziellen Strukturvariablen (sogenannte Knoten)**, die durch Linkzeiger miteinander verkettet sind, und eine Speicherstruktur aus linearen Tabellen.

  2. Struktur einer verknüpften Liste

  • Knoten : Ein Punkt in einem Stromkreis, der drei oder mehr Zweige verbindet
  • Knoten : Der Endpunkt oder Schnittpunkt einer Linie oder Kurve
  • Kopfknoten : pHead– Zeigervariable, die auf den Kopfknoten zeigt
  • Jeder Knotenpunkt besteht aus 2 Feldern:
    • Datenfeld – speichert die Informationen des Knotens selbst.
    • Zeigerfeld – speichert Zeiger auf nachfolgende Knoten (für einfach verknüpfte Listen).
  • Das Zeigerfeld des Endknotens wird als Zeichen für das Ende der verknüpften Liste auf NULL gesetzt (dargestellt durch einen Backslash).
Bild-20220321213735554

3. Die Merkmale der verknüpften Liste:

  1. Eine verknüpfte Liste ist eine Speicherstruktur, die zum Speichern linearer Listen verwendet wird.
  2. Die Knoten (Knoten) der verknüpften Liste werden durch Aufrufen der dynamischen Speicherzuweisungsfunktion entsprechend den Anforderungen zugewiesen, sodass die verknüpfte Liste nach Bedarf gedehnt und gekürzt werden kann , wodurch Speicher gespart wird, wenn die Anzahl der zu speichernden Daten unbekannt ist;
  3. Die Knoten der verknüpften Liste sind logisch kontinuierlich, aber der Speicher jedes Knotens ist normalerweise diskontinuierlich , sodass nicht sofort darauf zugegriffen werden kann, sondern nur Knoten für Knoten, beginnend mit dem Kopfknoten .

Erstellung verknüpfter Listen, Einfügen und Löschen von Knoten

1. Die Erstellung einer verknüpften Liste

Von Grund auf werden Knoten nacheinander erstellt und verbunden (verknüpft). Schritte:

  • Header-Zeiger deklarieren
  • Knoten (Struktur) generieren
  • Link (Kopfzeile einfügen oder am Ende anhängen)
  • geh im Kreis und im Kreis

Dynamische Speicherzuweisung von Knoten:

Bild-20220321215400711
pNew = malloc(sizeof(struct node));
pNew->data = 17;

Die dynamische Zuordnung verknüpfter Listenknoten bestimmt, dass die Speicherorte der Knoten im Speicher nicht unbedingt kontinuierlich sind.

typedef struct ListNode {
    
    //ListNode:链表节点
       int data;    //定义data变量值,存储节点值
       struct ListNode *next;   //定义next指针,指向下一个节点,维持节点连接
} LISTNODE;
LISTNODE* creat_FIFO_List(){
    
          //FIFO( First Input First Output)先进先出
    //step1:变量定义;
    //step2:变量初始化;
    //stept3:创建节点
    scanf("%d",&num);
    while(num != -1){
    
    
        pNew = malloc(sizeof(LISTNODE));
        if(pNew != NULL){
    
    
            pNew->data = num;
            //step4:细化:将新节点插入链表
        }
        scanf("%d",&num);
    }
    return pHead;
}    

Wenn der erste Knoten erstellt wird, wird pHeadauf ihn verwiesen;

Andernfalls wird der neu hinzugefügte Knoten an den Endknoten der verknüpften Liste angehängt.

Damit neue Knoten bequem an den Endknoten der verknüpften Liste angehängt werden können, muss ein Zeiger entworfen werden, der pTrailauf den Endknoten der verknüpften Liste zeigt.

Bild-20220321223330360

Anhängevorgang:

pTrail->pNext = pNew;

pTtrail = pNew;

Vollversion:

LISTNODE* creat_FIFO_List(){
    
    
    //step1+step2:定义变量+初始化
    int num;
    LISTNODE* pHead, pTrail, pNew;
    pHead = NULL;
    pTrail = NULL;
    pNew = NULL;
    
    printf("Input positive numbers, -1 to end.\n");
    
    scanf("%d",&num);
    while(num != -1){
    
    
        //step3:创建节点,分配内存
        //不管是pHead,pTrail,pNew,都是一个厂子出来的
        pNew = malloc(sizeof(LISTNODE));
        
        //step4:新节点插入链表
        //得先判断内存申请成没成功
        if(pNew != NULL){
    
    
            pNew->data = num;
            if(pHead == NULL){
    
    //pHead从头到尾就只是占了个名字
                pHead = pNew;
                pTrail = pNew;
            }else
            {
    
    
                pTrail->pNext = pNew;//追加
                pTrail = pNew;
            }
        }
        scanf("%d",&num);
    }
    pTrail->pNext = NULL;//设置链表结束标志
    return pHead;
}

2. Knotenzugriff

• So greifen Sie auf die Knoten in der verknüpften Liste zu: Da der Speicher der Knoten in der verknüpften Liste dynamisch zugewiesen wird, kann nicht über den Namen darauf zugegriffen werden, sondern nur über die Adresse des Knotens.

•Die Adresse eines Knotens wird im Adressfeld seines Vorgängerknotens aufgezeichnet. Wenn Sie also den n-ten Knoten besuchen möchten, müssen Sie zuerst den n-1-ten Knoten besuchen und das Adressfeld des Knotens lesen. Wenn Sie ihn besuchen möchten Wenn Sie den n-1-ten Knoten besuchen, müssen Sie zuerst den n-2-ten Knoten besuchen und so weiter, bis Sie den ersten Knoten besuchen. Und der Zeiger zeigt auf den ersten Knoten headPtr, sodass auf den ersten Knoten zugegriffen werden kann, sodass auch auf den zweiten Knoten und den dritten Knoten zugegriffen werden kann ...

void print_List(LISTNODE* pNew){
    
    
    if(pNew = NULL)
        printf("The list is empty\n");
    else{
    
    
        printf("The list is:\n");
        while(pNew != NULL){
    
    
            printf("%d-->",pNew->data);
            pNew = pNew->pNext;
        }
        printf("NULL\n\n");
    }
}

Hauptfunktion:

int main ()
{
    
    
    LISTNODE* pHead;
    pHead = creat_FIFO_List();
}

3. Dynamisches Hinzufügen und Löschen verknüpfter Listenknoten

Bild-20220321235258975
Zunahme
Bild-20220321235141499
if(pPre == NULL){
    
    
    //插在链表首结点
    pNew->pNext = pCurrent;
    pHead = pNew;
}
else if(pCurrent = NULL)
{
    
    
    //插在结尾
    pPre->pNext = pNew;
    pNew->pNext = NULL;
}
else
{
    
    
    //插在中间
    pPre->pNext = pNew;
    pNew->pNext = pCurrent;
}
löschen
if(pCurrent != NULL){
    
    
    if(pPre == NULL)
        pHead = pCurrent->pNext;
    else
        pPre->pNext = pCurrent->pNext;
    free(pCurrent);
}

• Das Einfügen und Löschen verknüpfter Listen ist hocheffizient und erreicht O(1). Für Daten, die nicht durchsucht werden müssen, sich aber häufig ändern und deren Obergrenze unvorhersehbar ist, wie z. B. Speicherpool, Prozessverwaltung des Betriebssystems, Trunk-Verwaltung des Netzwerkkommunikationsprotokollstapels usw., ist es absolut unmöglich, darauf zu verzichten Es.

• Die bemerkenswerteste Anwendung ist das Dateisystem. Wenn Sie die Festplatte formatieren, werden Sie zur Auswahl fat32und ntfsFormatierung aufgefordert. Tatsächlich können Sie hier die Größe und das Format des verknüpften Listenspeicherplatzes auswählen. Um die Systemeffizienz zu verbessern, müssen Sie manchmal eine Dateidefragmentierung durchführen, was bedeutet, dass die Daten einer Datei nicht unbedingt kontinuierlich gespeichert werden. Woher weiß das Betriebssystem also, diskontinuierliche Daten in einer Datei zu synthetisieren und Ihnen diese zur Verfügung zu stellen?

Grundlegende Funktionsweise einer verknüpften Liste

typedef struct ListNode{
    
    
    int data;
    struct ListNode* pNext;
}LISTNODE,*pLISTNODE;

Bild-20220322205638998

Grundlegende Operationen für verknüpfte Listen:

Verknüpfte Listen erstellen, Knoten abrufen (finden), Knoten einfügen, löschen, Knoten ändern usw.

**(**1) Die Erstellung einer verknüpften Liste besteht tatsächlich aus einer Reihe von Knoteneinfügungsvorgängen, und die Zeitspanne kann lang oder kurz sein. Daher können wir die Einfügungsknoten-Operationsfunktion entwerfen, die für verschiedene Situationen geeignet ist, um die Erstellungsfunktion zu realisieren.

(2) Wenn eine verknüpfte Liste zum Implementieren einer First-In-First-Out-Warteschlange verwendet wird, müssen wir jeden neuen Knoten am Ende der verknüpften Liste einfügen, was als Tail-Insertion bezeichnet wird .

(3) Wenn eine verknüpfte Liste zum Implementieren eines Last-In-First-Out-Stapels verwendet wird, müssen wir jeden neuen Knoten in den Kopf der verknüpften Liste einfügen, was als Header-Einfügung bezeichnet wird .

(4) Wenn eine verknüpfte Liste verwendet wird, um eine nach Elementwerten sortierte lineare Liste zu implementieren, müssen wir die verknüpfte Liste durchlaufen, den ersten Knoten finden, der größer als der Datenwert des neuen Knotens ist, und ihn zwischen diesem und dem einfügen Vorgängerknoten, der als geordnete Einfügung bezeichnet wird .

Verknüpfte Liste mit leeren Knoten

Der erste Knoten speichert eigentlich keine Daten, sondern nur die Adresse des nächsten Knotens. Daher die verlinkte Listekann niemals leer sein. Daher kann die Beurteilungslogik des Programms vereinfacht werden: Beim Einfügen eines Knotens oder Löschen eines Knotens muss nicht beurteilt werden, ob der Vorgängerknoten leer ist.

Bild-20220322211121590

Vorbereitung: Kopfzeiger und leerer Knoten:
/*创建一个带空节点的链表头*/
void create_ListHead(pLISTNODE* ppHead){
    
    
    *ppHead = malloc(sizeof(LISTNODE));
    if((*ppHead) != NULL){
    
    
        (*ppHead)->pNext = NULL;
    }
}
Hauptprogrammfragment:
int main (){
    
    
    LISTNODE pHead = NULL;
    creat_ListHead(&pHead);
    if(pHead == NULL){
    
    
        printf("List is not created.no memory avaliable\n");
        exit(-1);
    }...
}
//尾插
void append1(pLISTNODE,int);
void append2(pLISTNODE,int);
//头插
void appfront(pLISTNODE,int);
//中间插
void insert(pLISTNODE,int);
Schwanzstecker

Den ganzen Weg bis zum Endknoten durchqueren

Endpunktfunktionen:pTrail->pNext == NULL

Verbinden Sie den Schwanz:pTrail->pNext = pNew

Bild-20220322212823316

void append1(pLISTNODE p, int val){
    
    
    pLISTNODE pNew,pTrail;
    
    //创造新节点
    pNew = malloc(sizeof(pLISTNODE));
    if(pNew != NULL){
    
    //创造成功,,开始初始化
        pNew->data = val;
        pNew->pNext = NULL;
        
        /*找到尾结点,记为pTrail*/
        pTrail = p;
        while(pTrail->pNext != NULL){
    
    
            pTrail = pTrail->pNext;
        }
        /*接尾巴*/
        pTrail->pNext = pNew;
    }else
        printf("Not inserted. No memory avaliable.\n")
}
Optimierung der Schwanzeinfügung

Um das einfache Anhängen neuer Knoten an den Endknoten der verknüpften Liste zu ermöglichen, kann ein Endzeiger so entworfen werden, dass er pTrailauf den Endknoten der verknüpften Liste zeigt.

Anhängevorgang:

pTrail->pNext = pNew;

pTrail = pNew;

/*创建一个带空节点的表头*/
void create_ListHead(pLISTNODE* ppHead, pLISTNODE* ppTrail){
    
    
    *ppHead = malloc(sizeof(pLISTNODE));
    if((*ppHead) != NULL){
    
    
        (*ppHead)->pNext = NULL;
        *ppTrail = *ppHead;
    }
}
/*把一个新值插入到链表尾,利用尾指针*/
void append2( pLISTNODE* ppTrail,int val){
    
    
    pLISTNODE pNew;
    pNew = malloc(sizeof(pLISTNODE));
    
    if(pNew != NULL){
    
    
        pNew->data = val;
        pNew->pNext = NULL;
        
        (*ppTrail)->pNext = pNew;
        (*ppTrail) = pNew;
    }
    else
        printf("not inserted, no memory avaliable\n")
}

Bild-20220322230413341

Das Folgende ist falsch, der Parameter übergibt nur den Zeiger des Endknotens, warum nicht, wo nicht?

/*把一个新值插入到链表尾,利用尾指针*/
void append2( pLISTNODE pTrail,int val){
    
    
    pLISTNODE pNew;
    pNew = malloc(sizeof(pLISTNODE));
    
    if(pNew != NULL){
    
    
        pNew->data = val;
        pTrail = pNew;
}

Bild-20220322230333668

**Zusammenfassung:** Wenn Sie jemandem einen Wert zuweisen möchten, müssen Sie seinen Zeiger in der Funktion verwenden. Ebenso, wenn Sie den Wert in der Hauptfunktion ändern möchten:

Möchten Sie die Hauptfunktion ändern: Es ist notwendig, den entsprechenden Zeiger an die Funktion zu übergeben
gemeinsame Variable Zeiger der Ebene 1
Zeiger der Ebene 1 sekundärer Zeiger
sekundärer Zeiger Drei Hinweise
Erweitertes Wissen: Wie bedient man die gesamte verknüpfte Liste mit nur einem Zeiger?
Option 1: Ringverknüpfte Liste:

Richten Sie den Endknoten der verknüpften Liste auf den leeren Knoten des Kopfes der verknüpften Liste und ändern Sie den Endzeiger in den Kopfzeiger, nämlich:pTrail->pNext = pHead

Bild-20220322231450566

PS: Tatsächlich entspricht es einer Variablen a = b. Behalten Sie sie dann und bwerfen Sie sie weg a.

Denken Sie daran: Fan sieht, dass die Funktion der Funktion den externen Zeiger direkt zuweist. Wenn ich beispielsweise pTrail = pNextdiese Operation ausführen möchte, muss ich den sekundären Zeiger verwenden: (*ppTrail) = pNext;Hier betonen wir die Änderung des Namens der externen Variablen (einschließlich). die Zeigervariable).

Option 2: Zirkuläre doppelt verknüpfte Liste

Erweitern Sie das Zeigerfeld des Knotens der verknüpften Liste, um den Zeiger des Vorgängerknotens einzuschließen, und lassen Sie den Endknoten der verknüpften Liste auf den leeren Knoten des Kopfs der verknüpften Liste zeigen, dh auf die bidirektionale zirkuläre verknüpfte Liste.

Auf diese Weise ist der Vorgängerknoten des Knotens, auf den der Kopfzeiger zeigt, der Endknoten, und der Endzeiger wird nicht mehr benötigt.

Bild-20220322232017950
Stecker: Last in first out

Der neu hinzugefügte Knoten muss nach dem Kopf der verknüpften Liste/des leeren Knotens eingefügt werden.

pcurrent = pHead->pNext;

pHead->pNext = pNew;

pNew->pNext = pCurrent;

Ich denke du magst

Origin blog.csdn.net/weixin_61777209/article/details/123675058
Empfohlen
Rangfolge