总结
在学习单向链表的过程中,最费心的就是在初始化链表和销毁链表
因为其中涉及到了调用一个函数对在主函数中定义的指针变量进行内存的分配(malloc)和内存的销毁(free)
在C/C++的学习过程中,总结起来,遇到过二级指针的情况大概两种:一种是对链表的操作,另一种是对一个数组进行动态内存分配
关于链表反转
这个程序比较难理解并且抽象,因此用特殊法来走一遍程序
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int ElemType;
typedef int Status;
/*构建单向链表结构体*/
typedef struct LNode
{
ElemType data;
struct LNode *next;
}LinkList;
/*链表初始化*/
void initList(LinkList** L)//注意这里一定要传入二级指针!!!
{
*L = (LinkList*)malloc(sizeof(LNode));
if (!L)
{
exit(OVERFLOW);
}
else
{
(*L)->next = NULL;
printf("init success\n");
}
}
/*销毁链表*/
void destroyList(LinkList** L)//注意这里也一定要传入二级指针!!!
{
LinkList *temp;
while (*L)
{
temp = (*L)->next;//把本结点的指针域保存下来
free(*L);//释放本结点
*L = temp;//指针指向下一结点
}
}
/*清空链表*/
void clearList(LinkList* L)
{
LinkList* ptr = L->next;//保存头指针
L->next = NULL;//头指针->NULL
destroyList(&ptr);
}
/*判断链表是否为空*/
Status isEmpty(LinkList *L)
{
if (L->next)//为空
{
return TRUE;
}
else
{
return FALSE;
}
}
/*获取链表长度*/
int getLength(const LinkList& L)
{
int count = 0;
LinkList* ptr = L.next;//头结点指向的下一个结点
while (ptr)
{
++count;
ptr = ptr->next;
}
return count;
}
/*获取链表中具体位置的元素*/
ElemType getElem(const LinkList* L, int pos)
{
int j = 1;
LinkList* ptr = L->next;
if (pos<j || !ptr)
{
return ERROR;
}
while (ptr && pos>j)
{
++j;
ptr = ptr->next;
}
return ptr->data;
}
/*比较两元素是否相等*/
Status compare(ElemType a, ElemType b)
{
if (a == b)
{
return TRUE;
}
else
{
return FALSE;
}
}
/*查找指定元素的位置*/
int findElemPos(const LinkList* L, ElemType target)
{
int pos = 0;
LinkList* ptr = L->next;
while (ptr)
{
++pos;
if (compare(ptr->data, target))
{
return pos;
}
ptr = ptr->next;
}
return ERROR;
}
/*查找某个元素的前驱元素*/
ElemType findPreElem(const LinkList* L, ElemType target)
{
LinkList* ptr = L->next;//头指针的下一个结点
LinkList* ptrNext;
while (ptr->next)//如果第三个结点为空,也就是说明这个链表只有一个元素,那么就无所谓前置,后继了。
{
ptrNext = ptr->next;//对当前ptr复制一个副本,使此副本指向下一个结点。
if (compare(ptrNext->data, target))
{
return ptr->data;
}
ptr = ptrNext;
}
return ERROR;
}
/*查找某个元素的后继元素*/
ElemType findNextElem(const LinkList* L, ElemType target)
{
LinkList* ptr = L->next;
while (ptr->next)//保证后继元素不为空(不然找什么后继元素 - - ?)
{
if (compare(ptr->data, target))
{
return ptr->next->data;
}
ptr = ptr->next;
}
return ERROR;
}
/*插入结点*/
//pos:1~n。
Status insertElem(LinkList* L, int pos, ElemType target)
{
int currenPos = 0;
LinkList* ptr = L;
//使指针指向要插入的位置,所以此处判断条件不能:currentPos<=pos-1
while (ptr && currenPos<pos - 1)//注意到此处while里面的循环条件与删除某个结点的函数不同,原因:插入元素可以在头指针后插入,而删除某个结点的函数不能在链表中只有一个头指针的情况下进行删除操作
{
++currenPos;
ptr = ptr->next;
}
//比如在位置3插入,则此时ptr为第三个结点的地址,则ptr->next为第四个结点的地址
if ((!ptr) || currenPos > pos - 1)//头指针为空或者位置为0,1等
{
return ERROR;
}
LinkList* newNode = (LinkList*)malloc(sizeof(LNode));
newNode->data = target;
newNode->next = ptr->next;//链表的插入是在结点的后面插入!!!
ptr->next = newNode;
return OK;
}
/*删除某个结点*/
Status deleteElem(LinkList* L, int pos)
{
int currentPos = 0;
LinkList* ptr = L;
while (ptr->next&& currentPos<pos - 1)//ptr->next用来保征有东西可以删除,因为while 循环完ptr的地址是pos的前面一个结点,即ptr->next即为要删除的pos
{
++currentPos;
ptr = ptr->next;
}
//while 循环完ptr的地址是pos的前面一个结点,即ptr->next即为要删除的pos
if (!ptr->next || currentPos > pos - 1)
{
return ERROR;
}
LinkList* targetNode = ptr->next;//要删除的结点
ptr->next = ptr->next->next;
free(targetNode);
return OK;
}
/*遍历链表中的数据*/
void traverseList(const LinkList* L)
{
LinkList* ptr = L->next;
printf("\n遍历链表:\n");
while (ptr)
{
printf("%d ", ptr->data);
ptr = ptr->next;
}
}
//链表转(逆)置
Status reverseList(LinkList** L)
{
LinkList* preNode;
LinkList* currentNode = (*L)->next;//第一个结点
LinkList* nextNode = currentNode->next;
currentNode->next = NULL;//对第一个结点作特殊处理,因为它是反转后的最后一个结点
while (nextNode != NULL)//如果nextNode为空,则说明currentNode为最后一个结点
{
//遍历
preNode = currentNode;
currentNode = nextNode;
nextNode = nextNode->next;
//指针反转
currentNode->next = preNode;
}
(*L)->next = currentNode;
return OK;
}
/*链表中倒数第K个结点(最优算法)*/
LinkList* KthNodeFromEnd(LinkList* L, int K)
{
//防御性编程
if (NULL == L || 0 >= K)
{
return NULL;
}
LinkList* pNode = L;
LinkList* KthNode = L;
while (K - 1 > 0)//循环k-1次
{
if (pNode->next != NULL)
{
pNode = pNode->next;//使指针pNode先走K-1步
--K;
}
else
{
return NULL;
}
}
while (pNode->next != NULL)
{
pNode = pNode->next;
KthNode = KthNode->next;
}
return KthNode;
}
/*一次遍历找出链表中的中间结点*/
//思想与找倒数第K个结点一样:设立两个异步指针
//利用此思想,可以找出1/3,1/4,1/5位置的结点。
LinkList* findMidNode(LinkList* L)
{
LinkList* midNode = L;
LinkList* pNode = L;
while(pNode->next!=NULL&& pNode->next->next!=NULL)
{
midNode = midNode->next;
pNode = pNode->next->next;
}
return midNode;
}
/*主函数*/
int main()
{
LinkList* L;
//链表初始化
initList(&L);
//测试链表是否为空
if (isEmpty(L))
{
printf("linklist is empty\n");
}
for (int i = 0; i < 10; i++)
{
insertElem(L, i + 1, i + 1);
}
//查找链表倒数第3个结点的元素
LinkList* kthNode = KthNodeFromEnd(L, 3);
printf("链表倒数第3个结点的元素的元素为:%d\n", kthNode->data);
//第1个结点的元素
printf("the first element is %d\n", getElem(L, 1));
//元素5的位置坐标
printf("the element 5 at linklist is in %d\n", findElemPos(L, 5));
//删除指定位置的元素
if (deleteElem(L, 5))
{
printf("delete success\n");
}
//查找前驱元素
printf("the pre position of element 4 is %d\n", findPreElem(L, 4));
//查找后继元素
printf("the next position of element 4 is %d\n", findNextElem(L, 4));
//插入数据
if (insertElem(L, 2, 666))
{
printf("插入数据666成功!\n");
}
//遍历链表中的数据
traverseList(L);
//销毁链表
destroyList(&L);
return 0;
}