链表的结构
链表是用链节指针链在一起的自引用结构变量(称为结点)的线性集合,是线性表的一种存储结构。
结构变量通过指针"链"在了一起,具有前驱和后继关系。第一个结构变量的地址单独记录在一个指针里。
(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; /*被释放链表的头指针应该置为空*/
}