/*
一,数据结构书上对程序LinkedList.c的某些定义
1.线性表的链式存储结构
2.对于线性表的链式存储结构,不要求逻辑上相邻的元素在物理位置上也相邻,因此它没有顺序存储结构所具有的弱点,但也同时失去了顺序表可随机存取的优点。
3.链表中的几个定义:
(1)首结点,存放第一个有效数据的结点。
(2)尾结点,存放最后一个有效数据的结点,指针域为NULL。
(3)头结点,类型和首结点一样,首结点前面的那个结点,不存放有效数据,引入目的是为了方便链表的操作
(4)头指针,指向头结点的指针,存放头结点地址。
(5)尾指针,指向尾结点的指针。
(6)操作一个链表需要一个参数:头指针
二、写LinkList.c的目的:
1.本程序作为练习,可以提升写代码能力。
2.体现存储不一样,操作也就不一样。本文件为离散存储,比如删除一个元素后,其他地方的元素不需要想数组一样都要动。
3.泛型存储不一样,操作也一样。因为内部是通过C++的一个模版内部实现了,导致外部看起来的效果一样。这样才能很好地理解泛型。
4.注意广义算法与狭义算法,比如冒泡法排序,广义的算法就是指冒泡法,排序的方法,对于连续存储和离散存储都一样。而狭义的算法来说,连续存储和离散存储就不一样了,也
就是操作不一样。我认为,数组构建简单,操作起来较复杂,链表构建起来复杂,但有时操作起来简单。
三、关于非循环单链表的理解
1.最初根据郝斌c版数据结构视频学习,详见其大纲(自己打印得的资料)。
创建链表时:
(1)一般需要构造一个头结点(头指针为pHead);
(2)让一个尾指针指向头结点(pTail = pHead),并且pTail->pNext = NULL;
(3)在一个for循环中构造其他结点(用pNew分别指向这些结点),然后根据pTail和pNew的一些操作即可构建一个非循环单链表。
链表操作时:
(1)如果是编历,求长度,排序等操作,一般都是定义一个指针指向首结点,然后以此为基础进行操作。
(2)如果是插入,删除等操作,一般都是定义一个指针指向头结点,然后通过条件判断使之指向第pos-1的那个结点,然后以此为基础进行操作。也就是说插入删除伪算法的关键是找到插入/删除位置的前一个结点。另外,如果是在第pos个结点插入或是删除,要记住条件的判断,记住条件的判断,后面的具体插入或删除操作就相对简单:
如果是插入,判断条件首先if条件,插入的地方不能太靠前(i>pos-1),也不能太靠后,即第pos-1个结点必须存在(p==NULL),即if(i>pos-1 || NULL ==p),然后if条件上面的就是while条件,它是if条件的取反(NULL !=p && i<pos-1);
如果是删除,判断条件首先if条件,删除的地方不能太靠前(i>pos-1),也不能太靠后,即第pos个结点必须存在(p->pNext==NULL),即if(i>pos-1 || NULL ==p->pNext),然后if条件上面的就是while条件,它是if条件的取反(NULL != p->pNext && i<pos-1);
总之,时间长后又会搞忘,方法是隔一段时间就记忆一下。至于while和if的条件记忆方法可以用如下方法:
1)首先,插入和删除一个总的方法是定义一个指针指向pos-1个结点(pos是插入或删除位置)
2)如果是插入,由常识可知第pos-1的那个结点必须存在,而pos那个结点存不存在无所谓。所以先写if条件,从左边看位置不能小于等于0,右边位置pos-1的那个结点必须存在,从而得出if的条件(i>pos-1 || NULL ==p ),而if判断上面while的条件即是if条件取反,即(NULL !=p && i<pos-1)。
3)如果是删除,由常识可知第pos-1和pos的那个结点都必须存在,先写if条件,从左边看位置不能小于等于0,右边看位置pos的那个结点必须存在,从而得出if的条件(i>pos-1 || NULL ==p->pNext ),而if判断上面while的条件即是if条件取反,即(NULL !=p->pNext && i<pos-1)。
(3)判断尾结点等要注意。
2.注意,free(p)后,p指针变量一定要重新指向NULL,防止野指针出现,有效 规避误操作。另外指针在使用前一定要指向一个确定的地方,如果定义一个指针时不能指向一个确定的
地方,那么就指向NULL比较安全。
释放malloc分配的内存时,一定要释放申请时指向的那个指针。
*/
#include "LinkedList.h"
struct Node * creatLinkedList()
{
int len=0;
int i=0;
int val=0;
struct Node * pHead;
pHead = (struct Node *)malloc(sizeof(struct Node));//分配了一个不存放有效数据的头结点
if( NULL == pHead)
{
printf("creatLinkedList():malloc err!\n");
exit(-1);
}
struct Node * pTail = pHead;//定义一个指针指向头节点
pTail->pNext = NULL;//防止用户输入len=0,如果len=0,下面的for就不执行了,加上这一句是必须的。也就是说,pTail被假定为尾结点,如果链表一个结点都没有,那么它的头指针一定指向尾结点。
printf("please input the list len=");
scanf("%d",&len);
for(i=0; i<len; i++)
{
printf("please input data of %d:",i+1);
scanf("%d",&val);
struct Node * pNew =(struct Node *)malloc(sizeof(struct Node));
if( NULL == pNew )
{
printf("creatLinkedList():malloc err!\n");
exit(-1);
}
pNew->data = val;
pTail->pNext = pNew;//当i=0时,相当于头指针指向的头结点的指针域指向了pNew指向的那个结点
pNew->pNext = NULL;
pTail = pNew;//目的是for循环下一次时让上一次pNew指向结点的指针域指向新的pNew结点
}
return pHead;
}
//记住:头结点与首结点类型一样,头指针与其他struct Node*类型的指针类型一样
void traverseLinkedList(struct Node * pHead)
{
struct Node * p = pHead->pNext;//指向首结点
while( p != NULL )
{
printf("%d\n",p->data);
p = p->pNext;//不能写P++,因为不是连续的
}
printf("\n");
return;
}
int isEmptyLinkedList(struct Node * pHead)
{
if( NULL == pHead->pNext)
return 1;
else
return 0;
}
//链表有效结点的个数
int lengthLinkedList(struct Node * pHead)
{
struct Node *p = pHead->pNext;
int len=0;
while( p != NULL )
{
len++;
p = p->pNext;
}
return len;
}
void sortLinkedList(struct Node * pHead)
{
int i=0,j=0,t=0;
int length = lengthLinkedList(pHead);
struct Node * p=NULL;
struct Node * q=NULL;
for(i=0,p=pHead->pNext; i<length-1; i++,p=p->pNext)
for(j=i+1,q=p->pNext; j<length; j++,q=q->pNext)
if(p->data > q->data)
{
t = q->data;
q->data = p->data;
p->data = t;
}
return;
}
/* //改进版
void sortLinkedList(struct Node * pHead)
{
int t=0;
int length = lengthLinkedList(pHead);
struct Node * p,q;
for(p=pHead->pNext; p != NULL; p=p->pNext)
for(q=p->pNext; q !=NULL; q=q->pNext)
if(p->data > q->data)
{
t = q->data;
q->data = p->data;
p->data = t;
}
return;
}*/
//在pHead所指向链表的第pos个节点的前面插入一个新的结点,该节点的值是val, 并且pos的值是从1开始,如果链表有5个结点,可以在第6的地方插,这种情况相当于追加,但是不可以在第7个地方插入值,因为没有6何来7.
int insertLinkedList(struct Node * pHead,int pos,int val)
{
int i=0;
struct Node * p = pHead;
struct Node * q = NULL;
/*****************核心的地方,很健壮***********************/
/*
1.由于是否空链表无所谓,就取决于用户输入位置,分为如下三种情况:
(1)i的值大于pos-1,while的条件为假,if的条件为真,此函数将返回假。即用户输入pos的值小于等于0时,此函数会返回假。
(2)i的值等于pos-1,while和if的条件均为假,p初始又指向头结点(就算是空链表,这个时候p也不可能为空),pos又为1,所以p已经指向了pos-1个结点;由于不确定是不是空链表,第pos个结点即这里的第一个结点是否存在无法判断,但都不影响在头结点后面插入一个结点(要插入位置为1)。
(3)i的值小于pos-1,while的条件为真。说明此时pos>=2,在while循环中当i=pos-1时,while条件将为假,这个时候p已经指向了pos-1个结点(可以试数得出这个结论),即p->pNext存放了第pos个结点的地址,在接下来的if判断中,如果p=NULL,即第pos-1个结点为NULL,也就是说第pos-1个结点不存在,此时if条件为真,函数返回假,这就防止了链表有效长度为5,而pos=7的情况。如果p!=NULL,即pos-1个结点存在,而pos个结点是否存在无所谓。
2.总的说来,插入结点算法的侠义算法中的关键是找到插入位置的前一个结点,即让p指向插入位置的前一个结点。然后让p->pNext指向待插入结点,让待插入结点的pNext指向当时第
pos个结点。至于健壮性,即处理特殊情况,链表是否为空链表无所谓,第pos个结点是否存在无所谓。关键是用户输入的位置不能在第一个结点之前,也不能在有效结点+1之后。
*/
while(NULL !=p && i<pos-1)//针对&&,如果NULL!=p为假,程序不会去判断i<pos-1
{
p = p->pNext;
i++;
}
if(i>pos-1 || NULL ==p )//记忆方法:先记这里(用户输入的位置小了不行,大了也不行),然后while那就取反
return 0;
/*****************核心的地方,很健壮***********************/
//如果程序能执行到这一行说明p已经指向了第pos-1个结点,并且第pos-1个结点一定存在,但第pos个结点是否存在无所谓
//分配新的结点
struct Node * pNew =(struct Node *)malloc(sizeof(struct Node));
if( NULL == pNew )
{
printf("insertLinkedList():malloc err!\n");
exit(-1);
}
pNew->data = val;
q = p->pNext;
p->pNext = pNew;
pNew->pNext = q;
return 1;
}
int deleteLinkedList(struct Node * pHead, int pos, int * pVal)
{
int i=0;
struct Node * p = pHead;
/*****************核心的地方,健壮性很强***********************/
/*
1.如果是个空链表,如下的while条件为假,if的条件都为真,此函数将返回假。
2.如果不是空链表,就取决于用户输入位置,分为如下三种情况:
(1)i的值大于pos-1,while的条件为假,if的条件为真,此函数将返回假。
(2)i的值等于pos-1,while和if的条件均为假,p初始又指向头结点,pos又为1,所以p已经指向了pos-1个结点;既然不是空链表,就至少有一个结点,且pos=1,所以此时的第pos个结点(就是第一个结点)肯定是存在的。
(3)i的值小于pos-1,while的条件为真。说明此时pos>=2,在while循环中当i=pos-1时,while条件将为假,这个时候p已经指向了pos-1个结点(可以试数得出这个结论),即
p->pNext存放了第pos个结点的地址,在接下来的if判断中,如果p->pNext=NULL,即此链表的尾结点为第pos-1个结点,这时第pos个结点就是不存在的,此函数直接返回假,否则第pos个结点是存在的。
3.总的说来,删除结点算法的侠义算法中的关键是找到删除结点的前一个结点,即让p指向删除节点的前一个结点。至于健壮性,即处理特殊情况,主要要注意链表是否为空链表,并且第pos个结点一定要存在。
*/
while(NULL != p->pNext && i<pos-1)
{
p = p->pNext;
i++;
}
if( i>pos-1 || NULL == p->pNext)
return 0;
/*****************核心的地方,健壮性很强***********************/
//如果程序能执行到这一行说明p已经指向了第pos-1个结点,并且第pos个节点一定是存在的
struct Node * q = p->pNext;//q指向待删除的结点
*pVal = q->data;
//删除p节点后面的结点
p->pNext = p->pNext->pNext;//注意,链表要连接起来,必须把指针域指向下一个结点才行
free(q);//释放q所指向的节点所占的内存
q = NULL;
return 1;
}
/*
根据C专家编程,在遍历链表时正确释放元素的方法是使用临时变量存储下一个元素的地址。这样就可以安全地在任何时候释放当前元素,不必担心在取下一个元素的地址时还要引用它。
*/
void freeLinkedList(struct Node * pHead)
{
struct Node *p=NULL;
struct Node *temp=NULL;
for(p=pHead; p; p=temp)
{
temp = p->pNext;
free(p);
}
}
相关头文件:
#ifndef LINKEDLIST_H
#define LINKEDLIST_H
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *pNext;
};
/************FOR LinkedList**************/
struct Node* creatLinkedList();
void traverseLinkedList(struct Node* pHead);
int isEmptyLinkedList(struct Node * pHead);
int lengthLinkedList(struct Node * pHead);
void sortLinkedList(struct Node * pHead);
int insertLinkedList(struct Node * pHead,int pos,int val);
int deleteLinkedList(struct Node * pHead, int pos, int * pVal);
void freeLinkedList(struct Node * pHead);
/************FOR LinkedList**************/
#endif