数据结构——单链表

线性表的链式存储结构

在每个节点中除包含有数据域外,只设置一个指针域 ,用以指向其后继节点,这样构成的链接表称为线性单向链 接表,简称单链表,为了便于插入和删除运算的实 现,每个链表带有一个头节点,并通过头节点的指针唯一标识该链表。

单链表的缺点:当访问过一个节点后,只能接着访问它的 后继节点,而无法访问它的前驱节点。



单链表的相关运算

先定定义一下链表的结构体

typedef struct Lnode{
    int data;
    struct Lnode *next;
} LinkList;

插入和删除节点

  1. 插入节点

    LinkList *p //这是指向原来在链表中的
    LinkList s //这是需要被插入的节点
    s->next = p->next;
    p->next = s;
    //:只需修改相关节点的指 针域,不需要移动节点
    
  2. 删除节点

    LinkList *p// 指向链表 假设需要被删的元素是指 的后驱元素
    p->next = p->next->next;
    

建立单链表:

  1. 头插法建表
    特点:新节点插入到当前链表的表头上,最后得到的链表是一个与插入顺序相反的链表

     void CreateHeadList(LinkList *&L,int a[],int n){//头插法建表 
    	 LinkList *s;//指向LinkList类型数据的指针
    	 int i;
    	 L = (LinkList*)malloc(sizeof(LinkList));
    	 L->next  = NULL;//创建头节点,他的下一个是,UNLL
     
        for(i = 0;i<n;i++){
     		s = (LinkList*)malloc(sizeof(LinkList));//分配地址后,就是指针型变量;
     		//这里 给s分配地址要写在for循环里面,每次分配新的才是新的节点。 
       	  s->data =a[i];
      	   s->next = L->next;//
      	   L->next = s;
         // free(s); 也不可以释放,不然你刚创建的节点就没有了; 
    	 }
     }
    
  2. 尾插法建表:
    特点:生成的链表中节点的 次序和原数组元素的顺序一样。
    尾插示意图

    void CreateEndList(LinkList *&L,int a[],int n){
        LinkList *s,*r;
        int i;
        L = (LinkList*)malloc(sizeof(LinkList));//创建头节点
        r = L//r时钟指向尾节点,开始的时候,指的是头节点
        for(i = 0;i<n;i++){
            s = (LinkList*)malloc(sizeof(LinkList));
            s->data = a[i];
            r->next = s;
            r=s;
        }
        r->next = UNLL;//尾节点的next为UNLL
    }
    


线性表基本运算在单链表上的实现

  1. 初始化线性表 InitList(L)

    void InitList(*&L){
        L = (LinkList*)malloc(sizeof(LinkList));
        L->next = NULL;//不管是头插还是尾插,都一样,
      	  	           //尾插的话后面就改变了,而头插则一直都是UNLL。
    }
    
  2. 销毁线性表 DestroyList(L)

    void DistroyList(*&L){
        LinkList *pre = L,*p = L->next;
        while(p!=NULL){
            free(pre);
            pre = p;
            p = p->next;//遍历整个链表,释放每个节点
        }
        free(pre);//还有一个没被放
    }
    
  3. 判线性表是否为空表 ListEmpty(L)

    bool ListEmpty(*L){
        return (L->next=NULL);
    }
    
  4. 求线性表的长度 ListLength(L)

    int ListLenth(*L){
        int n=0;
        LinkList *p=L;
        while(L!=NULL){
            n++;
            p=p->next;//p移向下一个节点   
        }
        return (n);
    }
    
  5. 输出线性表 DispList(L)

    void DispList(LinkList *L) 
    {  LinkList *p=L->next; //p指向开始节点   
     while (p!=NULL){ //p不为NULL,输出*p节点的data值  
         printf("%d ",p->data); 
      	p=p->next; //p移向下一个节点   
     }   
     printf("\n"); 
    }
    
  6. 求线性表 L 中指定位置的某个数据元素 GetElem(L, I , e)

    bool GetElem(*L,int i,int &e){//e来保存该元素的值
        LinkList *p=L;
        for(j=0;j<i&&p!=NULL;j++){
            p=p->next;
        }
        if(p==NULL)
            return false;
        else{
            e = p->data;
            return true;
        }
       		
    }
    
  7. 按元素值查找 LocateElem(L,e)

    int LocateEleme(*L,int e){
        int i=1;
    	LinkList *p=L->next;
        while(p->data != e&& p!=NULL){
            i++;
            p = p->next;
        }
        if(p==NULL)
            return(0);
        else
            return (i);
        
    }
    
  8. 插入数据元素 ListInsert(L,i,e)

    bool ListInsert(*&L,int i,int e){
        LinkList *p=L,*s;
        int j=0;
        while(j<i-1&&p!=NULL){//因为单链表只能由前去元素道后驱元素,
       	                      // 所以如果想要在 第3个位置插要先找到第2个
            j++;
            p=p->next;
        }
        if(p==NULL)
            return false;        
        else
        {
         	s=(LinkList *)malloc(sizeof(LinkList));
            s->data=e;
            s->next = p->next;
            p->next = s;
            return true;
            
        }
    }
    
  9. )删除数据元素 ListDelete(L,i,e)

    bool ListDelete(LinkList *&L,int i;&e){
        LinkList *p=L,*s;
        int j = 0;
        while(j<i-1&&p!=NULL){
            j++;
            p = p->next
        }
        if(p==NULL)
            return false;
        else{
            s=p->next;
            if(s==NULL)
                return false;
            else{
                e=s->data;
            	p->next = s->next;
                free(s);//记得释放,是个好习惯
            }        	
        }
    }
    


一道例题

只有上面讲解好像还不理解,把代码合起来看看

有 一 个 带 头 节 点 的 单 链 表 L={a1,b1,a2,b2, …,an,bn} ,设计一个算法将其拆分成两个带头节点的单链表 L1 和 L2 : L1={a1,a2,…,an} , L2={bn,bn-1,…,b1} 要求 L1 使用 L 的头节点。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct Lnode{
    int data;
    struct Lnode *next;
} LinkList;

void CreateHeadList(LinkList *&L,int a[],int n){//头插法建表 
    LinkList *s;//指向LinkList类型数据的指针
    int i;
    L = (LinkList*)malloc(sizeof(LinkList));
    L->next  = NULL;//创建头节点,他的下一个是,UNLL
    
    for(i = 0;i<n;i++){
    	s = (LinkList*)malloc(sizeof(LinkList));//分配地址后,就是指针型变量;
    	//这里 给s分配地址要写在for循环里面,每次分配新的才是新的节点。 
        s->data =a[i];
        s->next = L->next;//
        L->next = s;
        // free(s); 也不可以释放,不然你刚创建的节点就没有了; 
    }
}

//销毁线性表
void DistroyList(LinkList *&L){
    LinkList *pre = L,*p = L->next;
    while(p!=NULL){
        free(pre);
        pre = p;
        p = p->next;//遍历整个链表,释放每个节点
    }
    free(pre);//还有一个没被放
}

//判断线性表是否为空 
bool ListEmpty(LinkList *L){
    return (L->next=NULL); 
}

//求线性表的长度
int ListLenth(LinkList *L){
    int n=0;
    LinkList *p=L;
    while(L!=NULL){
        n++;
        p=p->next;//p移向下一个节点   
    }
    return (n);
}

//输出线性表 
void DispList(LinkList *L) 
{  LinkList *p=L->next; //p指向开始节点   
 while (p!=NULL){ //p不为NULL,输出*p节点的data值  
    printf("%d ",p->data); 
  	p=p->next; //p移向下一个节点   
 }   
 printf("\n"); 
}

//求线性表指定位置的一个数值 
bool GetElem(LinkList *L,int i,int &e){//e来保存该元素的值
    LinkList *p=L;
    for(int j=0;j<i&&p!=NULL;j++){
        p=p->next;
    }
    if(p==NULL)
        return false;
    else{
        e = p->data;
        return true;
    }
   		
}

//产找第一个值为 e 的元素的位置 
int LocateEleme(LinkList *L,int e){
    int i=1;
	LinkList *p=L->next;
    while(p->data != e&& p!=NULL){
        i++;
        p = p->next;
    }
    if(p==NULL)
        return(0);
    else
        return (i);
    
}

//插入数据元素 
bool ListInsert(LinkList *&L,int i,int e){
    LinkList *p=L,*s;
    int j=0;
    while(j<i-1&&p!=NULL){//因为单链表只能由前去元素道后驱元素,所以如果想要在 第3个位置插要先找到第2个
        j++;
        p=p->next;
    }
    if(p==NULL)
        return false;        
    else
    {
     	s=(LinkList *)malloc(sizeof(LinkList));
        s->data=e;
        s->next = p->next;
        p->next = s;
        return true;
        
    }
}

void run(LinkList *L,LinkList *&L1,LinkList *&L2){
    // L1要有尾插法,L2用头插法
    LinkList *p=L->next,*r,*q;
    L1 = L;//L1 用L这个头节点
    r=L1;
    L2 = (LinkList *)malloc(sizeof(LinkList));
    L2->next = NULL;
    while(p!=NULL){
        r->next = p;
        r=p;
        //头插法完成
        
        /*
        q=p->next;
        q->next = L2->next;
        L2->next=q;
        p=p->next->next;
        //这个方法不行,这个虽然插好了,但是,如果要移动p的时候就不可以了
        //因为p->next指的是q,但是q的next已经指的不是原来L里的下一个了
        //这个主要是因为头插法改变插入元素的next值
        */
        

        p=p->next;//把p往后移一个
        q=p->next;
        p->next = L2->next;
        L2->next = p;
        p=q;//这样就保证了P可以连续的在L中移动
    }
    r->next=NULL;
}

int main(){
	int a[]={12,52,64,3,31,58,49,65,61,67,2,8,9,7,6,2,4,64};
	int n = 18;
	LinkList *L,*L1,*L2;
	CreateHeadList(L,a,n);
	DispList(L);
	/*
	int e = 18 ;
	ListInsert(L,10,e);
	DispList(L);
	*/
	run(L,L1,L2);
	DispList(L1);
	printf("***************\n");
	DispList(L2);
	printf("***************\n");
	DispList(L)//这里发现,L打印出来和L1一样
}



单链表原地置逆

最进有伙伴还问单链表怎么原地置逆,我这里提供两种思路,第一中,修改一下 next指针,第二种,我们都知道,头插法得到的单链表是反的,所以我们可以原地头插法;

  1. 法一:有头节点的单链表。

    void turn(LinkList *&L){
    LinkList *p=NULL,*pre=NULL,*s=NULL;
    s = L->next;
    while(s!=NULL){
    	p = s;
    	s = s->next;
    	p->next = pre;
    	pre = p;
    	}
    	L->next = pre;
    } 
    

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    根据图中的示例,以此类推,知道s指向NULL就停止啦(撒花)

  2. 原地头插法

void turn2(LinkList *&L){
	LinkList *p,*s;
	p = L->next;
	L->next = NULL;
	while(p!=NULL){
		s = p->next;
		p->next = L->next;
		L->next = p;
		p = s;
	}
	
}

这个就和头插法比较类似了,就不具体画图解释了;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值