'); } '); } 单向链表的基本操作 | Journey to paradise

单向链表的基本操作


单向链表的基本操作

     链表呢是一种十分基础的数据结构类型,分为单向链表,双向链表以及循环链表,今天我们就来讲讲单向链表的基本操作。

什么是单向链表

     链表是一种储存结构,包含数据域和指针域,一个数据域配一个指针域构成一个结点,数据域存放数据,指针域存放指针即地址,通过将下一个结点(后继结点)的地址存放在上一个结点(前驱结点)的指针域中将四处分散的结点连接起来,也就将分散的储存空间连接起来。 链表图示

单向链表的优缺点

     从上述描述我们可以看出,链表优点在于它的储存空间不用连续,因为它可以通过指针将各处分散的储存空间连接起来,而且链表的长度可以改变,是一种动态分配内存的模式,十分方便在任意位置添加删除结点。缺点在于不能随机访问结点,需要从头开始顺次一个结点一个结点的查找(单向链表),而数组就可以通过下标值直接找到需要的值。

单向链表的基本操作(增,删,改,查)

首先呢开始准备工作,引用头文件,定义类型...
    #include<stdio.h>             
    #include<stdlib.h>
    #define N 5                  //链表的长度(链表的位置从1开始) 
    #define X 1                  //待删除结点的起始位置
    #define Y 3                  //待删除结点的末位置 
    typedef struct node{
        int data;
        struct node* next;
    }node,* List;

    List L1,L2;

创建链表并赋值

此处链表的头指针数据域不存放数据
    List CreateLinearList(List L1)       //创建单向链表并向链表中写入值 
    {
        int i,count;
        node *q;
        
        L1=(List)malloc(sizeof(node));
        L1->next=0;
        q=L1;
        
        printf("Enter the number of your elements in the list:\n");
        scanf("%d",&count);
        printf("Enter %d elements for your linear list:\n",count);
        
        for(i=0;i<count;i++)                         
        {
            node* p=(node*)malloc(sizeof(node));  //p是创建的新结点,往链表后面插,q指向链表的尾结点 
            scanf("%d",&p->data);
            p->next=0;
            q->next=p;
            q=p;
        }
        q->next=0;
        
        return L1;	
    }

插入结点

链表头插法图解
链表尾插法图解
链表指定位置插法图解

头插法

void InsertByHead(List L)
{
    node* p=(node*)malloc(sizeof(node))        //创建新结点 
    
    printf("Enter the element to insert by head:\n");
    scanf("%d",&p->data);                                 //输入要插的数并存放在新结点的数据域 
    
    p->next=L->next;                                     //将新结点插入链表头部 
    L->next=p; 
}

尾插法

void InsertByTail(List L1) 
{
    node *p,*q;                        //p从头遍历找到链表的尾结点,q是要插入的新结点 
    p=L1;
    q=(node*)malloc(sizeof(node)) ;
    printf("Enter an element to insert to the tail:\n");
    scanf("%d",&q->data);
    q->next=0;
    
    while(p->next)
      {
        p=p->next;
      }
    p->next=q;
}

指定位置插

void InsertByPosition(List L)             //向链表指定位置插值 
{
    int position,i;                       //position记录插值的位置 
    node *q,*p=(node*)malloc(sizeof(node));      //q遍历链表找到待插位置的前一个结点,p存放待插的值 
    q=L;
    
    printf("Enter the position and value to be inserted:\n");
    scanf("%d%d",&position,&p->data);
    
    if(position<=0)                        //位置不能为负值 
    {
        printf("Invalid Position!\n");
        exit(-1);
    }
    for(i=1;i<position;i++)
    {
        q=q->next;                       
        if(q==0)                       //位置超过链表的最后一位的后一位 
        {
            printf("Invalid position!\n");
            exit(-1);
        }
    }
    
    p->next=q->next;
    q->next=p;
}

顺序表按顺序插

void InsertSorted(List L) //有序表按顺序插值
{
    T e;
    printf("Enter an element to be inserted:\n");
    scanf("%d",&e);
    node* p=(node*)malloc(sizeof(node));
    node* q=L->next;
    p->data=e;
    p->next=0;
    
    while(q->next&&q->next->data<e)
    {
        q=q->next;
    }
    p->next=q->next;
    q->next=p;
} 

删除结点

按位置删除一个结点

void DeleteElementByPosition(List L)
{
    int i,position;
    node *d,*p=L;               
    printf("Enter the position of value to be deleted:\n");
    scanf("%d",&position);
    if(position<=0)
    {
        printf("Invalid position!\n");    // 查询位值小于零,不合法,退出 
        exit(-1);
    } 
        for(i=1;i<position;i++)
        {
            p=p->next;     //p记录待删结点的前一个结点 
            if(p->next==0)
            {
                printf("Invalid position!\n")
                exit(-1);       //查询位置超过链表最后一个位置,不合法,退出 
            } 
        }
        
        d=p->next;              //d记录待删结点
        p->next=p->next->next;   
    
        free(d);                 //释放待删结点 
}

按值删除一个结点,删第一个

bool DeleteElement(List L)
{
    int e;
    node *p=L,*q;
    printf("Enter the value to be deleted:\n");
    scanf("%d",&e);
    
    while(p->next!=0&&p->next->data!=e)
    {
        p=p->next;
    }
    
    if(p->next!=0)                   //若p->next!=0说明未遍历到链表最后一个结点 ,找到e 
    {
        q=p->next;                   //q指向待删值所在结点
        p->next=q->next;
        free(q);
        
        return true;
    }

    else
    {
        printf("There are no such value in the list!\n");
        
        return false;
    }
} 

按值删除结点,全删

void DeleteElements(List L)
{
    //反复调用函数DeleteElement(),直到返回值为false,即链表中已经没有待删值 
    while(DeleteElement(L)) 
    ;
}

按值删除结点,只留一个

void DeleteUnique(List L)
{
    int e;
    node *p=L->next,*q;
    printf("Enter the value to be deleted:\n");
    scanf("%d",&e);
    
    while(p!=0&&p->data!=e)
    {
        p=p->next;
    }
    if(p!=0)  //若p->next!=0说明未遍历到链表最后一个结点 ,找到e ,p指向第一个e所在的结点 
    {                          
        DeleteElements(p);  //保留第一个e,调用DeleteElements(p),删除其他的e 
    }
    else
    {
        printf("There is no such value in the list!\n");
    }
} 

删去链表中最大值

void InsertMaxToListHead(List L1)           //将链表的最大值移到头部 
{
    node *max=L1->next,*p=L1,*q;
                
    while(p->next)                                
    {
        if(p->next->data > max->data)
        {
            max=p->next;
            q=p;
        }
        p=p->next;
    }	
    q->next=max->next;
    max->next=L1->next;
    L1->next=max;
}

void DeleteMaxOfList(List L1)           // 删除链表中最大元素 
{
    node *max;
    
    InsertMaxToListHead(L1);            //调用InsertMaxToListHead()函数将最大值移到头部 
    max=L1->next;
    L1->next=max->next;                 //删去结点L1->next即最大值结点 
    free(max);
}

更改结点的值

      在删除结点程序上稍作更改即可

1.通过位置更改

2.通过值更改

打印链表

void PrintList(List L)           //打印链表 
{
    int i;
    node *p=L->next,*q=L;
    while(p)                     //通过循环打印链表结点的值
    {
        printf("%d ",p->data);
        p=p->next;
        if(p==L->next)           //如果是遍历循环链表,p往后遍历到表头处结束 
        {
            break;
        }
    }
    printf("\n");
}

其他操作:

求表长

int Len(List L)
{
    node *q=L;
    int count;
    for(count=0;q->next!=0;q=q->next,count++);
    
    return count; 	
}

链表转置

头插法转置单链表:
void Reverse(List L)        //将链表转置 
{
    int i;
    node *tail,*p;           //tail指向链表的尾结点,p指向尾结点的前一个结点
    
    for(i=1;i<Len(L);i++)    //每次都从链表头开始,找到链表尾结点 
    {
        tail=L->next;
        p=L;
    while(tail->next)         //遍历链表找到尾结点 
        {
            tail=tail->next;
            p=p->next;
        } 
            
            p->next=0;         //头插法将链表的尾结点插到链表头部 
            tail->next=L->next;
            L->next=tail;
    } 
    
}
    

递归法转置单链表:

void Reverse(List L)
{
    if(L==null||L->next==null) return;//单链表只有一个结点
    List last = Reverse(L->next);
    L->next->next=L;
    L->next=null;
}

迭代法转置单链表

// 反转以 a 为头结点的链表
ListNode reverse(ListNode a) {
    ListNode pre, cur, nxt;
    pre = null; cur = a; nxt = a;
    while (cur != null) {
        nxt = cur.next;
        // 逐个结点反转
        cur.next = pre;
        // 更新指针位置
        pre = cur;
        cur = nxt;
    }
    // 返回反转后的头结点
    return pre;
}

链表排序(升序)

冒泡法排序:
void OrderListByAscending(List L1)      // 5.将链表按升序排序 
{
    int i;
    node* p=L1;
    
    for(i=1;i<N;i++)
    {
        InsertMaxToListHead(p);             //多次调用InsertToMaxHead()将p链表的最大值移到p链表的头部 
        p=p->next;                          //p往后移直至移到L1链表的最后一个结点 
    }
}

链表相并

void Add(List1,List2)     //两有序链表相并(并到链表L1上) 
{
    
    node *p=L1->next,*q=L2->next,*tail=L1;  
    //p遍历L1的结点,q遍历L2的结点,tail指向合并链表的尾结点
    
    while(p&&q)                //当遍历到某链表的尾结点时,循环结束 
    {
        if(p->data < q->data)  //tail指向p->data,q->data中较小的结点 
        {
            tail->next=p;
            tail=p;
            p=p->next;		
        }
        
        else if(p->data > q->data)
        {
            tail->next=q;
            tail=q;
            q=q->next;
    
        }
        else
        {
            tail->next=q;
            tail=q;
            q=q->next;
            p=p->next;
            
        }
    }
    
    tail->next=(q==0?p:q);      //tail->next指向未遍历完的链表结点  
} 

上述有关链表操作的main函数根据需要写

一个有趣的实例:多项式相加

思路类似于有序链表的合并

完整代码如下

#include<stdio.h>
#include<stdlib.h>
typedef struct node{
    int fac;         //多项式项的系数部分 
    int e;           //多项式项的指数部分 
    struct node* next; 
}node,* Poly;

void AddPoly(Poly P1,Poly P2) //两升序序多项式相加 
{
    node *p=P1->next,*q=P2->next,*tail=P1;  
    //p遍历L1的结点,q遍历L2的结点,tail指向合并链表的尾结点
     
    while(p&&q)          //当遍历到某链表的尾结点时,循环结束 
    {
        if(p->e < q->e)  //tail指向系数较小的结点 
        {
            tail->next=p;
            tail=p;
            p=p->next;		
        }
         
        else if(p->e > q->e)
        {
            tail->next=q;
            tail=q;
            q=q->next;
    
        }
        else                    //相同指数项系数相加 
        {
            p->fac=p->fac+q->fac; 
            tail->next=p;
            tail=p;
            p=p->next;
            q=q->next;
            
        }
    }
    
    tail->next=(q==0?p:q);      //tail->next指向未遍历完的链表结点  
 } 
 
 Poly CreatePoly(Poly P)
 {
     int count,i;
     P=(node*)malloc(sizeof(node));
     P->next=0;                                //头结点数据域不存放数据 
    node *m=P;
     
    printf("Enter the number of your node:\n");
    scanf("%d",&count);
    
    printf("Enter coefficients and index:\n"); //输入多项式的系数和指数部分 
    for(i=0;i<count;i++)
    {
        node *q=(node*)malloc(sizeof(node));     
        scanf("%d%d",&q->fac,&q->e);           //输入项的系数和指数
        q->next=0;
         
        m->next=q;                             //m记录待插的位置(链尾),q是待插的结点 
        m=q;
     } 
     
     return P;
 }
 
 void PrintPoly(Poly P)
 {
    int i;
    node *m=P->next;
    
    while(m)        
    {
        printf("%dx^%d + ",m->fac,m->e);
        m=m->next;
    }
    printf("\n");

 }
 int main(Void)
 {
     Poly P1,P2;
     P1=CreatePoly(P1);
     printf("P1=");
     PrintPoly(P1);
     P2=CreatePoly(P2);
     printf("P2=");
     PrintPoly(P2);
     AddPoly(P1,P2);
     printf("P1+P2="); 
     PrintPoly(P1);
     
     return 0;	
 }

运行结果无误。

链表操作的递归实现

      事实上尾递归都可以用循环实现,而递归算法十分占内存,所以上述链表中需要遍历的各种操作是没有必要用递归实现的,这里我们只做一点了解:
代码如下(链表的头结点数据域不为空)
    #include<iostream>
    #include<stdio.h>
    using namespace std;
    
    typedef int T;
    
    typedef struct node{
        T data;
        node * next;
    }node,*List;
    
    void Create(List &h) //将链表分为两部分:头结点和头结点后面部分
    {                    //而头结点后面部分又可以分为同样的两部分,依次类推  
        T e;
        cin>>e;
        if(e<=0)
        {
            h=0;        //输入负数为结束标志 
            return;
        }
        
        h=new node;
        h->data=e;
        
        Create(h->next);    //递归创建头结点后面的链表 
    }
    
    //此方式不是尾递归,可以不用查找链表的尾结点来遍历倒序输出链表,是一个很好的倒序输出的方法
    void ReverseTraverse(List h)     //将链表倒序输出
    {                         //若递归到尾结点的next则递归结束 
        if(0==h)
        {
            return;
        }
        ReverseTraverse(h->next);  //过程为:先输出头结点后面部分的链表,再输出头结点
        
        cout<<h->data<<" ";  //而头结点后面部分的链表又可以分为头结点和头结点后部分的链表,输出顺序如上一步骤
    } 
    
    void Traverse(List h)   //将链表正序输出 
    {
        if(0==h)             //若递归到链表尾结点的后面,则返回 
        {
            return;
        }
            
        cout<<h->data<<" ";  //过程为:先输出头结点,再输出头结点后面部分的链表
        Traverse(h->next);   //而头结点后面部分的链表又可以分为头结点和头结点后部分的链表,输出顺序如上一步骤
            
    }
    
    int Len(List &h)
    {
        if(0==h)
        {
            return 0;           //链表为空时结束递归,返回值 
        }
        return Len(h->next)+1;  // 链表的长度为头结点后面部分构成链表的长度加1(头结点) 
    }
    
    node* Locate(List h,T e)   //查找 
    {
        if(0==h)                //递归到链表的尾结点,没找到待查值,返回地址为空 
        {
            return 0;
        } 
        if(h->data==0)          //找到待查值,返回该值的位置 
        {
            return h;
        }
        
        return Locate(h->next,e);  //查找分为两部分:头结点和头结点后面部分的链表,依次类推 
    } 
    
    void Destroy(List h)       //删除链表 
    {
        if(0==h)                 //尾结点递归完毕,返回 
        {
            return;
        }
        Destroy(h->next);        //删除分为两部分:删除头结点和头结点后面部分的链表 
        delete h;
    } 
    
    int main(void)
    {
        List h=0;
        Create(h);
        ReverseTraverse(h);
        cout <<endl;
        
    return 0;
    } 

至此大功告成!代码较为幼稚QAQ,思路值得学习~


文章作者: 涂爽
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 涂爽 !
评论
  目录
Copyright © 2020 涂爽
  站点总字数: 145.4k 字 本站总访问量 人次, 访客数 人.
载入运行时间... 鄂ICP备20005056