数据结构初步:单链表

链表的结构

链表是用链节指针链在一起的自引用结构变量(称为结点)的线性集合,是线性表的一种存储结构。
结构变量通过指针"链"在了一起,具有前驱和后继关系。第一个结构变量的地址单独记录在一个指针里。
(1)headPtr──指向链表首结点的指针变量。
(2)每个结点由2个域组成:
数据域──存储结点本身的信息。
指针域──存储指向后继结点的指针(针对单向链表)。
(3)尾结点的指针域置为NULL(用反斜杠表示),作为链表结束的标志。
注:链表的结点在逻辑上是连续的,但是各结点的内存通常是不连续的,因此不能立即被访问到,只能从头结点开始逐结点访问

链表的存储,打印,删除(释放)

以下是链表的简单存储代码,

#include<stdio.h>
#include<stdlib.h>
/****************************************************
结构体说明
功能:链表的一个单元,包括存储的数据,链接下一个单元的链节指针
参数:data, *nextptr
****************************************************/ 
typedef struct node{
	int data;
	struct node *nextptr;
}LISTUNIT;
int main()
{
	LISTUNIT *headptr=NULL,*lastptr=NULL,*currentptr=NULL;//前驱,后继,媒介 
	int num;
	scanf("%d",&num);
	while(num!=-1)
	{
		if(headptr==NULL)
		{
			headptr=malloc(sizeof(LISTUNIT));//直接用头指针申请空间 
			headptr->data=num;
			currentptr=headptr;
			lastptr=headptr;
		}
		else 
		{
			currentptr=malloc(sizeof(LISTUNIT));
			currentptr->data =num;
			lastptr->nextptr =currentptr;//将上一节点的后继与当前节点相连 
			lastptr=currentptr;//移动后继指针到当前节点 
		}
		//printf("%d\n",currentptr->data ); //不设置其他函数检验该部分的语句 
		scanf("%d",&num);
	}
	lastptr->nextptr =NULL;
	return 0;
 } 
 /*****************************************
 循环的第二种写法,直接以currentptr申请空间,少了几行代码。
 ****************************************/
 while(num!=-1)
	{
	    currentPtr=malloc(sizeof(LISTNODE)); /*分配结点内存*/ 
 	    if (currentPtr!=NULL)//还可判断申请空间是否成功
		{/*插入结点*/
 	        currentPtr->data=num;	
    		if (headPtr==NULL)
			{   /*若创建的是首元结点*/
    		    headPtr=currentPtr;
                lastPtr=currentPtr; 
            } 	   
          	else
			{
          	    lastPtr->nextPtr=currentPtr;  /*将结点连上链表尾结点*/
              	lastPtr=currentPtr;   /*使lastPtr指向当前链表的最后一个结点*/
            }
        }
        scanf("%d",&num);
 	}

以下是链表打印函数的代码,正确,但还可以更好

void LISTprint(LISTUNIT *headptr)
{
	LISTUNIT *currentptr=NULL,*lastptr=NULL;
	currentptr=headptr;
	lastptr=headptr;
	if(headptr==NULL)printf("NULL!!!");
	while(lastptr->nextptr!=NULL)//可以只用一个指针
	{
		printf("%d\n",currentptr->data  );
		currentptr=lastptr->nextptr ;
		lastptr=currentptr;
	}
	printf("%d\n",lastptr->data );
}
/***********************************************
以下是老师的代码
**********************************************/
/**************************************************
函数:void printList(LISTNODEPTR currentPtr) ;
功能:顺次输出链表中各节点数据;
参数:LISTNODEPTR currentPtr  指向链表首元结点的指针 
**************************************************/
void printList(LISTNODEPTR currentPtr)
{
    if (currentPtr==NULL)//!!!考虑到链表为空的情况
    	   printf("the list is empty\n");
    else
	{
	   	printf("the list is:\n");
   	   	while(currentPtr!=NULL)
		{
   	        printf("%d-->",currentPtr->data);
            currentPtr=currentPtr->nextPtr;
        } 
        printf("NULL\n\n");   
    }
}

以下是链表删除代码

void LISTdelete(LISTUNIT *headptr)
{
	LISTUNIT *currentptr=NULL;
	while(headptr!=NULL)
	{
		currentptr=headptr;
		headptr=headptr->nextptr ;
		free(currentptr);
	}	
}

以下是主函数代码对比

int main()
{
	LISTUNIT *headptr=NULL;
	headptr=LISTSAVE();
	LISTprint(headptr);
	LISTdelete(headptr);
	LISTprint(headptr);
	return 0;
}

结果如下。。前面三函数正常,最后删除又打印打印出了这一堆,乱七八糟的数据,删除链表后headptr应该赋为NULL。
在这里插入图片描述
以下是老师的主函数

int main()
{
	LISTNODEPTR		headPtr = NULL ;
    
	headPtr = createFIFOList1();
	printList(headPtr) ;
	destroyList(headPtr) ; 
	headPtr = NULL ;//!!!
	
	return 0;
}

链表的插入

实际链表第一个节点不存放数据,只存放下一节点地址。因此该链表永远不可能为空。从而可以简化程序的判断逻辑:插入节点或删除节点时不用判断前驱节点为空的情况。
在这里插入图片描述

创建一个带空节点的链表头

/*创建一个带空节点的链表头*/
//传递指向指针的指针
void createListHead (LISTNODEPTR * headPtrPtr){
	*headPtrPtr = malloc(sizeof(LISTNODEPTR));
	if ((*headPtrPtr) != NULL){
		(*headPtrPtr)->nextPtr = NULL;
	} 
}

尾插

(1)无尾指针插法
在这里插入图片描述

/*把一个新值插入到链表尾部*/
void insertEnd1 (LISTNODEPTR sPtr, ElementType value){
	LISTNODEPTR newPtr, previousPtr, currentPtr;
	/*动态分配新节点,将新值存入*/
	newPtr = malloc(sizeof(LISTNODEPTR));
	if (newPtr != NULL){
		newPtr->data = value;
		newPtr->nextPtr = NULL;
		/*找到尾节点,记为前驱指针previousPtr*/
		previousPtr = sPtr ;
		currentPtr = sPtr->nextPtr;
		while (currentPtr != NULL){
			previousPtr = currentPtr;
			currentPtr = currentPtr->nextPtr;
		}

		/*在尾节点后链接新节点*/
		previousPtr->nextPtr = newPtr;
	}
	else 
	    printf("Not inserted. No memory avaliable.\n");
}

(2)有尾指针插法
在这里插入图片描述

单链表改进

1、首尾指针相连,形成环状链表
2、双链表:将链表的指针域改成包含前驱节点指针

头插

在这里插入图片描述

有序插节点

在这里插入图片描述

按值删除节点

在这里插入图片描述

删除多重节点

/*删除所有值为value的节点,成功则返回被删元素个数;否则返回0。*/
int deleteNodes(LISTNODEPTR headPtr,ElementType value){
    LISTNODEPTR previousPtr,currentPtr;
     int count=0;
    previousPtr = headPtr;
    currentPtr = headPtr->nextPtr; /*将空节点的后继传给currentPtr*/
    
    while ( currentPtr != NULL){  /*遍历整个链表来删除多个value值节点*/
        /*查找待删除节点,若找到,则由currentPt指向该节点*/
        while (currentPtr!=NULL &&  currentPtr->data!=value){ 
              previousPtr=currentPtr;
              currentPtr=currentPtr->nextPtr;
        }
      if (currentPtr!=NULL){ /*如果找到要删除的节点*/
	previousPtr->nextPtr= currentPtr->nextPtr;
             free(currentPtr); //释放节点
             currentPtr = previousPtr->nextPtr;//删除多重节点的关键
 	count++;
        } 
    }
    return count;
}

链表不可以排序后二分查找

链表不能直接定位到第k个节点,所以链表不能进行二分查找,除非借助于存储所有节点指针的指针数组。

链表逆序

在这里插入图片描述
在这里插入图片描述

void reverseList(LISTNODEPTR headPtr, LISTNODEPTR * lastPtrPtr) {
    	LISTNODEPTR firstPtr,previousPtr,currentPtr,tempPtr;  

	/*若是空链表,不用逆序*/
   	if(headPtr->nextPtr == NULL)  	return ;

	/*空节点处理*/
	firstPtr = headPtr->nextPtr;
    headPtr->nextPtr = *lastPtrPtr;

	/*第一个数据节点处理 */	
	currentPtr = firstPtr->nextPtr; 	previousPtr = firstPtr; 
	firstPtr->nextPtr=NULL;  //很重要,反向后的链表结束标记
	/*循环处理后续所有节点*/
    while ( currentPtr != NULL ) {
	   tempPtr = currentPtr->nextPtr;
    	   currentPtr->nextPtr = previousPtr;/*反向连接*/
    	   previousPtr = currentPtr;/*下次循环准备工作*/
    	   currentPtr = tempPtr;
    }        
    *lastPtrPtr = firstPtr; /*返回更新后的尾指针*/ 
}

链表归并

在这里插入图片描述

/*有序(升序)链表的归并函数*/
void mergeSortList(LISTNODEPTR head1Ptr, LISTNODEPTR * last1PtrPtr, LISTNODEPTR head2Ptr, LISTNODEPTR * last2PtrPtr )
  参数:head1Ptr指向链表1的头指针;
 		   head2Ptr指向链表2的头指针;
 		   last1PtrPtr指向链表1的尾指针;
 		   last2PtrPtr指向链表2的尾指针;
   返回值:无,因为空节点的添加保证了头指针不会变化,尾指针的变化可以通过二级指针带回。
void mergeSortList(LISTNODEPTR head1Ptr, LISTNODEPTR * last1PtrPtr, LISTNODEPTR head2Ptr, LISTNODEPTR * last2PtrPtr )
{
	LISTNODEPTR first1, first2, pre1, temp2;
	first1 = head1Ptr->nextPtr;  first2 = head2Ptr->nextPtr;
	pre1 = head1Ptr; 
	while ( first1 != NULL && first2 != NULL){
      	if ( first2->data < first1->data){
			temp2 = first2->nextPtr;
			pre1->nextPtr = first2;	pre1 = first2;
			first2->nextPtr = first1;
			first2  = temp2;
		} else {
			pre1 = first1;	first1 = first1->nextPtr; 
		}
	}//end while
	if (first2 != NULL ){
		pre1->nextPtr = first2;
//也可以	(*last1PtrPtr)->nextPtr = first2;
		*last1PtrPtr = *last2PtrPtr;
	} 
	/*修正链表2为空链表*/
	head2Ptr->nextPtr = NULL;
	*last2PtrPtr = head2Ptr;
}

环形链表

在这里插入图片描述

/*把一个新值插入到环形链表尾部,利用尾指针*/
void insertCirEnd2 (LISTNODEPTR * lastPtrPtr , ElementType value){
	LISTNODEPTR newPtr;
	/*动态分配新节点,将新值存入*/
	newPtr = malloc(sizeof(LISTNODEPTR));
	if (newPtr != NULL){
		newPtr->data = value;
		newPtr->nextPtr = (*lastPtrPtr)->nextPtr ;
		/*直接在尾节点后链接新节点*/
		(*lastPtrPtr)->nextPtr = newPtr ;
		*lastPtrPtr = newPtr;
	}
	else 
	    printf("Not inserted. No memory avaliable.\n");
}

释放环形链表

void destroyCirList(LISTNODEPTR * headPtrPtr)
{
    LISTNODEPTR tempPtr, currentPtr;
	currentPtr = (*headPtrPtr)->nextPtr;
	while ( currentPtr != *headPtrPtr ){
        tempPtr = currentPtr;
        currentPtr = currentPtr->nextPtr;
        free(tempPtr);
    }
    free(*headPtrPtr);
	*headPtrPtr = NULL; /*被释放链表的头指针应该置为空*/
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值