数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。
从上可知:链表中的元素在内存中是不连续的存储,每个结点只能也只有它能知道下一个结点的存储位置。
单链表是线性表链式存储的一种,其存储不连续。它的数据结构中包含两个变量:数据和指向下一个结点的指针。每个结点(尾结点除外)都只知道它的下一个结点的存储地址。一个单链表必须有一个头指针,用来指向单链表中的第1个元素结点(或头结点);否则链表会在内存中丢失。
1 单链表的建立
1.1 不带头结点,从头部插入
#include <iostream>
#include <stdlib.h>
using std::cin;
using std::cout;
using std::endl;
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
//不带头结点,从头部插入
ListNode* CreateList_Head(int n)
{
if(n==0)
return NULL;
ListNode* pHead=NULL;
ListNode* pNext=NULL;
for(int i=0;i<n;i++)
{
ListNode* pNew=(ListNode*)malloc(sizeof(ListNode));
if(pNew!=NULL)
{
int value;
cin>>value;
pNew->val=value;
if(i) //始终从第1个结点之后的位置开始插入
{
pNew->next=pNext->next;
pNext->next=pNew;
}
else //第1个结点
{
pNew->next=NULL;
pHead=pNew;
pNext=pNew;
}
}
}
return pHead;
}
1.2 不带头结点,从尾部插入
//不带头结点,从尾部插入
ListNode* CreateList_Tail(int n)
{
if(n==0)
return NULL;
ListNode* pHead=NULL;
ListNode* pNext=NULL;
for(int i=0;i<n;i++)
{
ListNode* pNew=(ListNode*)malloc(sizeof(ListNode));
if(pNew!=NULL)
{
int value;
cin>>value;
pNew->val=value;
if(i) //依次向后插入
{
pNext->next=pNew;
pNext=pNext->next;
}
else //第1个结点
{
pHead=pNew;
pNext=pNew;
}
}
}
pNext->next=NULL;
return pHead;
}
1.3 带头结点,从头部插入
//带头结点,从头部插入
ListNode* CreateListHead_Head(int n)
{
if(n==0)
return NULL;
//创建头结点,头结点的val变量没有赋值
ListNode* pHead=(ListNode*)malloc(sizeof(ListNode));
if(pHead!=NULL)
{
pHead->next=NULL;
for(int i=0;i<n;i++)
{
ListNode* pNew=(ListNode*)malloc(sizeof(ListNode));
if(pNew!=NULL)
{
int value;
cin>>value;
//始终从头结点之后(向第1个元素结点的位置)插入
pNew->val=value;
pNew->next=pHead->next;
pHead->next=pNew;
}
}
}
return pHead;
}
1.4 带头结点,从尾部插入
//带头结点,从尾部插入
ListNode* CreateListHead_Tail(int n)
{
if(n==0)
return NULL;
ListNode* pHead=(ListNode*)malloc(sizeof(ListNode)); //创建头结点,头结点的val变量没有赋值
if(pHead!=NULL)
{
ListNode* pNext=pHead;
pNext->next=NULL;
for(int i=0;i<n;i++)
{
ListNode* pNew=(ListNode*)malloc(sizeof(ListNode));
if(pNew!=NULL)
{
int value;
cin>>value;
pNew->val=value; //从头结点之后依次向后插入
pNext->next=pNew;
pNext=pNext->next;
}
}
pNext->next=NULL;
}
return pHead;
}
【注:】当单链表不带头结点时,头指针指向链表的第1个元素结点;当单链表带头结点时,头指针指向链表的头结点。
2 单链表的反转(逆置)
2.1 反转整个单链表
//反转整个链表
ListNode* RevListNode(ListNode* pHead)
{
if(pHead==NULL)
return NULL;
ListNode* pRevHead=pHead;
ListNode* pPrev=pHead;
ListNode* pNext=pHead->next;
while(pNext)
{
ListNode* pTemp=pNext->next;
pNext->next=pPrev;
pPrev=pNext;
if(pTemp==NULL)
pRevHead=pNext;
pNext=pTemp;
}
pHead->next=NULL;
return pRevHead;
}
2.2 反转单链表的部分区间元素
//反转链表的某一区间结点[startid,endid],结点下标从1开始
ListNode* RevPartListNode(ListNode* pHead,int startid,int endid)
{
if(pHead==NULL||startid<1||startid>endid)
return NULL;
ListNode* pRevPartHead=pHead;
ListNode* pPrev=pHead;
ListNode* pPPrev=NULL;
ListNode* pNext=pHead->next;
int indexid=1;
while(pNext)
{
if(indexid>=startid)
{
if(indexid<endid)
{
ListNode* pTemp=pNext->next;
pNext->next=pPrev;
indexid++;
//下标到达反转终止下标或者到达链表的尾部
if(indexid==endid||pTemp==NULL)
{
if(startid>1)
{
pPPrev->next->next=pTemp;
pPPrev->next=pNext;
pRevPartHead=pHead;
}
else //反转起始下标为1时链表的头指针才会改变
{
pHead->next=pTemp;
pRevPartHead=pNext;
}
break;
}
pPrev=pNext;
pNext=pTemp;
}
}
else
{
pPPrev=pPrev;
pPrev=pNext;
pNext=pNext->next;
indexid++;
}
}
return pRevPartHead;
}
3 打印单链表的所有结点元素值
//打印链表的所有结点值
void PrintListNode(ListNode* pHead)
{
if(pHead==NULL)
return ;
ListNode* pNext=pHead;
while(pNext)
{
cout<<pNext->val<<" ";
pNext=pNext->next;
}
cout<<endl<<endl;
}
接着测试上述代码的正确性
#define ListNodeLen 10
int main()
{
//不带头结点从尾部插入
ListNode* pHead=CreateList_Tail(ListNodeLen);
cout<<endl<<endl;
PrintListNode(pHead);
PrintListNode(RevListNode(pHead));
PrintListNode(RevPartListNode(pHead,1,4));
PrintListNode(RevPartListNode(pHead,2,5));
PrintListNode(RevPartListNode(pHead,6,10));
return 0;
}
输入测试用例(单链表)为{0->1->2->3->4->5->6->7->8->9},测试结果为
从上图可知,除了打印初始单链表和打印全部反转后的单链表的结果符合预期外,打印部分反转后的单链表的结果显得比较奇怪,都只有一个元素0值。
原因:在这5个打印操作中,链表结点指针pHead没有改变,它始终指向元素值为0的链表结点。自从第2个打印中单链表全部反转操作后,元素值为0的链表结点成为了新链表的尾结点;此时pHead指向的“单链表”只包含元素值为0的这一个结点。故后面的3个部分反转后的打印结果只有一个元素0值。
更改测试程序重新测试
#define ListNodeLen 10
int main()
{
//不带头结点从尾部插入
ListNode* pHead=CreateList_Tail(ListNodeLen);
cout<<endl<<endl;
PrintListNode(pHead);
ListNode* pRevHead=RevListNode(pHead);
PrintListNode(pRevHead);
pHead=RevListNode(pRevHead);
PrintListNode(RevPartListNode(pHead,1,4));
PrintListNode(RevPartListNode(pHead,2,5));
PrintListNode(RevPartListNode(pHead,6,10));
return 0;
}
测试结果如下所示
从上可知,单链表顺利地完成了全部反转和部分反转。例如第3个打印操作将初始单链表的前4个结点反转。由于pHead始终指向元素值为0的结点,故在第4个打印操作时“单链表”只有7个结点,接着反转它的第[2,5]个结点。由于“单链表”长度只有7,故最后的反转操作在第[6,7]个结点上。