带头结点的线性链表的基本操作

来源:互联网 发布:百度免费空间域名 编辑:程序博客网 时间:2024/05/11 17:57

持续了好久,终于有了这篇博客,链表的操作需要借助图像模型进行反复学习,这里尽可能的整理并记录下自己的思考,以备后面复习,和大家分享。需要说明的是,我们从实际应用角度出发重新定义了线性表。

一. 定义

从上一篇文章可以看到,由于链表在空间的合理利用上和插入、删除时不需要移动等优点,因此在很多场合下,它是线性表的首选存储结构。然而,它也存在某些实现的缺点,如求线性表的长度时不如顺序存储结构的缺点。因此,从实际应用角度出发重新定义了线性表。
一个带头结点的线性链表类型定义如下:

//结点类型typedef struct Node{    ElemType   data;    struct Node *next;}Node,*PNode;//链表类型typedef struct List{    PNode head; //指向线性链表的头结点    PNode tail; //指向线性链表的尾结点    size_t size; //线性表中数据元素的个数}List;

二、函数实现的难点

我们基于一个改良的链表类型,进行常见的基本操作,这里将就每个函数实现的重难点找出来,使其核心精髓展现在我们眼前:

InitList(List *list)

该函数实现了将我们定义的链表进行初始化,使其头指针、尾指针都指向头结点(空表),链表的大小size为0.
这里写图片描述

void push_back(List *list,ElemType x)

尾插入,首先构建一个节点,然后将其插入链表尾部。此时体现出来该种数据结构的优势,我们无需从链表首部直接操作,直接找到了链表尾部对其操作。

void push_front(List *list,ElemType x)

头插,核心是用于在头结点和第一个结点之间插入新的子节点。当然,如果插入前为空表,需修改链表的尾结点指向第一个结点。(因为是对头部操作,我们需要留意尾结点)

void pop_back(List *list)

尾部删除,首先判断链表是否非空,然后不停的删除尾结点。当然,需要找到尾结点的前驱,我们需要顺序遍历。

void pop_front(List *list)

头部删除,首先判断链表是否非空,然后不停的删除头结点第一个结点。需要注意,如果删除到只剩头结点时,需要将尾指针指向头结点。(因为是对头部操作,我们需要留意尾结点)

void insert_val(List *list, ElemType x)

插入值,也即插入一个结点。前提是有序,按值插入(需考虑插入到尾结点之后情况:此时更新尾结点指针)。

Node* find (List *list, ElemType key)void delete_val(List *list,ElemType key)

删除值,调用了find方法,我们首先找到指向当前要删除值的结点,然后将下一结点的值赋值给当前结点,使当前结点指向下一结点的next,删除下一结点,从而变相的删除了当前结点。
当然,我们也可以通过顺序遍历,找到当前要删除值结点的前驱,然后删除该节点。

void sort(List *list)

本篇文章的重点:排序。之前的实现中,我们都是通过某种排序方法,交换结点中的data,从而实现。本篇文章我们通过交换结点的方法来实现。
这里写图片描述
首先,我们把整个单链表断开,已排序部分仅仅包括头结点和第一个结点(尾指针指向第一个结点),待排序部分从第二个结点开始余下的部分。
我们每次从待排序部分取出一个结点s,将其值和已排序部分顺序比较,从而找到要插入位置的前驱p,插入前要更新q为待排序链表的第一个结点。然后尾插即可。

void reverse(List *list)

本篇文章的重点:反转。同上,我们还是把整个单链表断开分为两部分:已排序部分仅仅包括头结点和第一个结点(尾指针指向第一个结点),待排序部分从第二个结点开始余下的部分。
我们每次从待排序部分取出一个结点给p,然后在头结点和第一个结点之间头插实现。

三、代码

sList.h

#ifndef SLIST_H__#define SLIST_H__#include <stdio.h>#include <malloc.h>#include <assert.h>typedef int ElemType;//结点类型typedef struct Node{    ElemType   data;    struct Node *next;}Node,*PNode;//链表类型typedef struct List{    PNode head;    PNode tail;    size_t size;}List;void InitList(List *list);void push_back(List *list,ElemType x); //尾插void push_front(List *list,ElemType x); //头插void pop_back(List *list); //尾删void pop_front(List *list); //头删void insert_val(List *list, ElemType x); //按值插入Node* find (List *list, ElemType key);//查找某值int length(List *list); //长度void delete_val(List *list,ElemType key); //删除值void sort(List *list);//升序void reverse(List *list);//反转void clear(List *list);//清除void destroy(List *list);//销毁void show_list(List *list);#endif // SLIST_H__

sList.cpp

#include "sList.h"void InitList(List *list){    list->head = list->tail = (Node *)malloc(sizeof(Node));    assert(list->head != NULL);    list->head->next = NULL; //头结点下一个为空    list->size = 0;}//1.更新新尾结点(一直对尾结点操作,故不需考虑)void push_back(List *list,ElemType x){    Node *s = (Node *)malloc(sizeof(Node));    assert (s != NULL);    s->data = x;    s->next = list->tail->next;    list->tail->next = s; //原尾结点指向s    list->tail = s;//更新为新尾结点    //由于一直对尾指针考虑,因此这里不需要考虑size=0    list->size++;}//1.在头结点之后插入作为第一个结点(需要考虑尾结点)void push_front(List *list,ElemType x){    Node *s = (Node *)malloc(sizeof(Node));    assert (s != NULL);    s->data = x;    s->next = list->head->next; //s结点指向第一个结点    list->head->next = s; //头结点指向第一个结点    //插入时,如果是第一个结点(size=0)则需修改list->tail指针指向s    if(list->size == 0)    {        list->tail = s;    }    list->size++;}//1.判断非空 2.使倒数第二个结点为新尾结点(一直对尾结点操作,故不需考虑)void pop_back(List *list){    if(list->size == 0)    {        printf("error: seqList is empty\n");        return;    }    Node *p = list->head;    while (p->next != list->tail) { p = p->next; }    free(list->tail);//删除旧尾结点    list->tail = p;//新尾结点    list->tail->next = NULL;//新尾结点next为NULL    list->size--;}//1.判断非空 2.删除头结点之后第一个结点 3.需要考虑尾结点void pop_front(List *list){    if(list->size == 0)    {        printf("error: seqList is empty\n");        return;    }    Node *q = list->head->next;    list->head->next = q->next;    free(q);    //删除时,如果只剩第一个结点(size=1)则需修改list->tail指针指向头结点    if(list->size == 1)    {        list->tail = list->head;    }    list->size--;}//前提:有序。按值插入(考虑插入到尾结点之后情况)void insert_val(List *list, ElemType x){   Node *s = (Node *)malloc(sizeof(Node));   assert(s != NULL);   s->data = x;   s->next = NULL;   Node *p = list->head;   while(p->next != NULL && p->next->data <x)        p=p->next;   //若插入到尾结点,则更新尾结点   if(p->next == NULL)   {        list->tail = s;   }   //找到该插入位置的前一位   s->next = p->next;   p->next = s; //指向新创建的结点   list->size++;}Node* find (List *list, ElemType key){    Node *p = list->head->next;    //利用短路条件,注意顺序    while( p!=NULL && p->data != key)  { p = p->next; }    return p;}int length(List *list){    return list->size;}void delete_val(List *list,ElemType key){    if(list->size == 0)        return;    Node *p = find(list,key);    if(p == NULL)    {        printf("delete val not exist\n");        return ;    }    if(p == list->tail)    {        pop_back(list);    }else    {        //将要删除数据的next拿过来替换值,取出next的有用信息后free掉q        Node *q = p->next;        p->data = q->data;        p->next = q->next;        free(q);        list->size--;    }}//把整个单链表断开,把剩下链表的结点根据值升序尾插void sort(List *list){    if(list->size==0 || list->size == 1)        return ;    Node *s = list->head->next; //指向第一个结点    Node *q = s->next;//指向第二个结点    list->tail = s;    list->tail->next = NULL; //断开链表    //按值插入    while(q!=NULL)    {        s = q;        q = q->next;       //p指针为已排序指针,s为待排序指针       Node *p = list->head;       while(p->next != NULL && p->next->data < s->data)            p=p->next;       //若插入到尾结点,则更新尾结点       if(p->next == NULL)       {            list->tail = s;       }       //尾插       s->next = p->next;       p->next = s; //指向新创建的结点    }}//把整个单链表断开,把剩下链表的结点按值头插void reverse(List *list){    if(list->size==0 || list->size == 1)        return ;    Node *p = list->head; //指向第一个结点    Node *q = p->next;//指向第二个结点    list->tail = p;//指向第一个结点    list->tail->next = NULL; //断开链表    while(q != NULL)    {        p = q;        q = q->next;        p->next = list->head->next ;        list->head->next  = p; //在头结点和第一个结点直接插入    }}void clear(List *list){    if(list->size ==0)        return ;    Node *p = list->head->next;    while(p!=NULL)    {        list->head->next = p->next;        free(p);        p = list->head->next;    }    list->tail = list->head;    list->size = 0;}void destroy(List *list){    clear(list);    free(list->head);    list->tail = list->head =NULL;}void show_list(List *list){    Node *p = list->head->next;    while(p)    {        printf("%d--->",p->data);        p = p->next;    }    printf("Nul\n");}

main.cpp

#include "sList.h"/*break语句通常用在循环语句和开关语句中。当break用于开关语句switch中时,可使程序跳出switch而执行switch以后的语句;如果没有break语句,则会从满足条件的地方(即与switch(表达式)括号中表达式匹配的case)开始执行,直到switch结构结束。当break语句用于do-while、for、while循环语句中时,可使程序终止循环。而执行循环后面的语句,通常break语句总是与if语句联在一起。即满足条件时便跳出循环。*/int main(){    List mylist;    InitList(&mylist);    int select;    ElemType num;    Node *p;    while(select)    {        printf("* [1] push_back                [2] push_front  *\n");        printf("* [3] show_list                [4] pop_back     *\n");        printf("* [5] pop_front                [6] insert_val   *\n");        printf("* [7] find                     [8] length       *\n");        printf("* [9] delete_val               [10] sort        *\n");        printf("* [11] reverse                 [12] clear       *\n");        printf("* [13] destroy                 [0] quit_system  *\n");        printf("please choose:>");        scanf("%d",&select);        if(select == 0)  break;        switch(select)        {            case 1:                printf("push_back: input numbers(-1 erminate):");                while(scanf("%d",&num),num != -1)                {                    push_back(&mylist,num);  //尾插                }                break;            case 2:                printf("push_front: input numbers(-1 erminate):");                while(scanf("%d",&num),num != -1)                {                    push_front(&mylist,num);  //头插                }                break;            case 3:                show_list(&mylist);                break;            case 4:                pop_back(&mylist); //尾删                break;            case 5:                pop_front(&mylist); //头删                break;            case 6:                printf("input inset value:>");                scanf("%d",&num);                insert_val(&mylist,num); //升序插值                break;            case 7:                printf("input find value:>");                scanf("%d",&num);                p = find(&mylist,num); //查找某值                if(p == NULL)                    printf("value %d not exist\n",num);                break;            case 8:                printf("length:%d\n",length(&mylist));  //长度            case 9:                printf("input delete value:>");                scanf("%d",&num);                delete_val(&mylist,num); //删除值                break;            case 10:                sort(&mylist); //升序                break;            case 11:                reverse(&mylist); //反转                break;            case 12:                clear(&mylist); //清除                break;            case 13:                destroy(&mylist); //销毁                break;            default:                printf("error,choose again\n");                break;        }    }    printf("Hello world!");    return 0;}