链表属于线性表的范畴。线性表有顺序的存储结构实现,代码如下:
typedef int ElemType;
typedef struct{
Elem elem[maxsize];
int length;
}SqList;
同时线性表还有链式存储结构,包括单链表,双链表,循环链表,静态链表。
单链表:只能向前遍历,最后一个节点为NULL,判断结束用while(p)。编程的时候只要处理next指针就行了。
双链表:可以两个方向遍历,双链表的尾节点指针为NULL。在编程的时候注意要处理next和prev指针,其他和单链表一样。
循环链表:只能向前遍历,但是最后一个指向头结点。这样判断结束的时候用while(p!=head)。编程的时候注意最后一个节点的处理。
静态链表:借用一维数组来描述线性链表。数组中的一个分量表示一个节点,同时使用游标代替指针指示下一个节点在数组中的位置,第0个节点可以不存数,表示Head节点。这种存储结构需要首先分配很大的空间,但是在进行插入和删除的时候不需要移动元素,仅需要修改游标就行了。
typedef char ElemType[10]
typedef struct{
ElemType data;
int next;
}StaticList[MaxSize];
本文只简单的写了链表的操作。
链表的实现
好久没用C语言写代码了,手生了,得找点感觉,不然面试就悲剧了。本文尽量采用C语言的风格,可是写出的代码还是夹杂着C++风格,关键我用的visual studio。简单的总结一下,后面复习的时候用到。链表实现的时候主要注意一下几点:
(1)链表编代码的时候不要忘了最后一个节点加上“NULL”,C++里面有构造函数,构造出来next就是NULL,所以出现这个问题比较少。
(2)链表的插入和删除操作,需要扫一遍链表,先判定是否存在该节点。
(3)链表的创建有头插法和尾插法两种。头插法创造出来的链表是反的,尾插法就是顺序的。
(4)不要忘了释放链表。(本文没有编写释放)
(5)在子函数里面分配节点有如下两种方法:
//方法1
int *test()
{
int *t = (int*)malloc(sizeof(int));
return t;
}
//方法2
void test(int **t)
{
*t = (int*)malloc(sizeof(int));
}
test(&pHead);//调用
链表实现代码:
这边只实现了几个简单操作,其他应该也不难。我这边实现的代码都有一个额外的头结点。
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
typedef int ElemType;
typedef struct ListNode{
ElemType data;
struct ListNode* next;
}LinkList;
void InitNode(LinkList **head)
{
*head = (LinkList*)malloc(sizeof(LinkList));
(*head)->next = NULL;
}
void CreateList(LinkList* pHead,ElemType *arr,int len)
{
LinkList *pCurrent = pHead;
for (int i=0;i<len;++i)
{
LinkList *newNode = (LinkList*)malloc(sizeof(LinkList));
if(!newNode){
--i;
continue;
}
newNode->data = arr[i];
pCurrent->next = newNode;
pCurrent = newNode;
}
pCurrent->next = NULL;//别忘掉
}
void DisplayList(LinkList* pHead)
{
if(!pHead)return;
for(pHead=pHead->next;pHead;pHead=pHead->next)
printf("%d ",pHead->data);
}
链表的常用操作
(1)链表的排序
方法1:选择排序
这里我简单借助了选择排序,我并没有直接对节点进行重组,而只是去交换节点的值,代码测试没有问题。
void SortList(LinkList* pHead)
{
LinkList *minPtr;
for(LinkList* ptrA=pHead->next;ptrA&&ptrA->next;ptrA =ptrA->next){
minPtr = ptrA;
for(LinkList *ptrB =ptrA->next;ptrB;ptrB = ptrB->next){
if(ptrB->data<minPtr->data)
minPtr = ptrB;
}
if(minPtr!=ptrA)
{
ElemType temp = ptrA->data;
ptrA->data = minPtr->data;
minPtr->data =temp;
}
}
}
方法2:(插入排序)
void InsertSortList(LinkList* pHead)
{
if (!pHead->next)return;
LinkList *p,*q = pHead->next->next;
pHead->next->next = NULL;//将第一节点断开
while(q){
LinkList *tmp = q->next;
//p->next,p节点不为NULL
for(p = pHead;p->next&&p->next->data<q->data;p=p->next);
q->next = p->next;
p->next = q;
q = tmp;
}
}
(2)链表的反序操作
画个图简单的说明下。
void ReverseList(LinkList *pHead)
{
if(NULL == pHead->next)return;
LinkList *ptr = pHead->next->next;
LinkList *pCurrent = pHead->next;
pCurrent->next = NULL;//一定要
while(ptr){
LinkList *tmp = ptr->next;
ptr->next = pCurrent;
pHead->next = ptr;
pCurrent = ptr;
ptr = tmp;
}
}
贴一个递归的代码,感觉写的不是很好,有没有更好的:
LinkList* ReverseListRecursively(LinkList *pHead,LinkList **pReverseHead)
{
if(pHead==NULL) return NULL;
if(pHead->next == NULL)
{
*pReverseHead = pHead;
return pHead;
}
LinkList *pRet = ReverseListRecursively(pHead->next,pReverseHead);
pRet->next = pHead;
pHead->next = NULL;
}
呵呵:这哥们代码不错:点击打开链接
- LinkNode *reverse_link(LinkNode *head)
- {
- if(head == NULL)
- return NULL;
- LinkNode *prev , *curr , *reverse_head , *temp;
- prev = NULL , curr = head;
- while(curr->next)
- {
- temp = curr->next;
- curr->next = prev;
- prev = curr;
- curr = temp;
- }
- curr->next = prev;
- reverse_head = curr;
- return reverse_head;
- }
- LinkNode *reverse_link_recursive(LinkNode *head)
- {
- if(head == NULL)
- return NULL;
- LinkNode *curr , *reverse_head , *temp;
- if(head->next == NULL) // 链表中只有一个节点,逆转后的头指针不变
- return head;
- else
- {
- curr = head;
- temp = head->next; // temp为(a2,...an)的头指针
- reverse_head = reverse_link_recursive(temp); // 逆转链表(a2,...an),并返回逆转后的头指针
- temp->next = curr; // 将a1链接在a2之后
- curr->next = NULL;
- }
- return reverse_head; // (a2,...an)逆转链表的头指针即为(a1,a2,...an)逆转链表的头指针
- }
(3)两个有序链表的合并操作
void MergeList(LinkList* pHead1,LinkList*pHead2,LinkList* pHead)
{
LinkList *tmp = pHead1;
pHead1 = pHead1->next;
free(tmp);
tmp = pHead2;
pHead2 = pHead2->next;
free(tmp);
while(pHead1&&pHead2){
if(pHead1->data<pHead2->data){
pHead->next = pHead1;
pHead = pHead->next;
pHead1 = pHead1->next;
}
elseif(pHead1->data>pHead2->data){
pHead->next = pHead2;
pHead = pHead->next;
pHead2 = pHead2->next;
}
else{
pHead->next = pHead1;
pHead1 = pHead1->next;
tmp = pHead2;
pHead2 = pHead2->next;
free(tmp);
pHead = pHead->next;
}
}
if(pHead1)pHead->next = pHead1;
if(pHead2)pHead->next = pHead2;
}
(4)三个有序链表A,B,C。现在设计一种算法使A中只保留A,B,C中的相同的节点,其他释放掉
void CommmonNode(LinkList* hA,LinkList* hB,LinkList *hC)
{
LinkList *pA = hA->next,*pB = hB->next,*pC = hC->next;
hA->next = NULL;
LinkList *pCur = hA;
while(pA){
while(pB&&pB->data<pA->data)pB = pB->next;
while(pC&&pC->data<pA->data)pC = pC->next;
if(pC->data==pA->data&&pB->data==pA->data){
pCur->next = pA;
pCur = pA;
pA = pA->next;
}
else{
LinkList *tmp = pA;
pA = pA->next;
free(tmp);
}
pCur->next = NULL;
}
}
(5)求出链表中倒数第K个元素
解法:很清晰就是用两个指针,第一个指针先跑到第K个元素,然后两个指针一起跑。但是面试写代码要注意考虑表的长度不够或者空表的现象。
ElemType TheLastKthNode(LinkList *pHead,int k)
{
if(!pHead&&!pHead->next){
cerr<<"Empty list"<<endl;
return -1;
}
LinkList *pFirst = pHead->next;
LinkList *pSecond = pHead->next;
int i;
for(i=0;pFirst&&i<k;i++,pFirst=pFirst->next);
if(i!=k){
cerr<<"No enough nodes"<<endl;
return -1;
}
for(;pFirst;pFirst=pFirst->next,pSecond=pSecond->next);
return pSecond->data;
}
(6)求两个链表的交点问题:用长度的方法或者判断链表是否带圈。(不给出代码)
(7)链表没有头结点,但是要删除一个节点。(编程之美狸猫换太子)
(8)测试代码
int main()
{
LinkList *pHead1,*pHead2,*pHead;
InitNode(&pHead1);
InitNode(&pHead2);
InitNode(&pHead);
ElemTypearr1[]={3,2,5,4};
ElemType arr2[]={3,5,2,8};
CreateList(pHead1,arr1,4);
CreateList(pHead2,arr2,4);
SortList(pHead1);
SortList(pHead2);
DisplayList(pHead1);
cout<<endl;//c++
DisplayList(pHead2);
cout<<endl;//c++
MergeList(pHead1,pHead2,pHead);
DisplayList(pHead);
system("pause");
return 0;
}
希望后面还有链表算法的时候,还能增加在这里。
后记
新手,勿笑。