第一类:链表的考察(链表的插入,删除,排序和逆转等)

来源:互联网 发布:上古世纪帅哥捏脸数据 编辑:程序博客网 时间:2024/05/06 09:17

数据的存储结构在计算机中主要有四种基本的存储方式:顺序存储、链接存储、索引存储、散列存储方式。我们知道,顺序存储的优点是支持随机访问结构中的数据,但是在插入和删除方面运算时会造成大量数据的移动,效率较低;而链接存储却有效的补充了顺序存储的不足,当需要频繁的插入和删除时,效率得到提高。而索引存储附件了索引表,在访问时通过索引项(由关键字和地址组成值对)去访问数据的存储地址,而散列存储的就是hash的存储方式,即根据关键字通过hash转换求的数据的存储地址。在面试的过程中,链表是其中重要考察内容,下面让我们来一起学习和交流常见的链表操作。

首先,让我们重温一下单链表的数据结构:

typedef int datatype;typedef struct ListNode{    datatype data;//结点数据域    ListNode *next;//结点指针域}ListNode,*Linklist;

一、在单链表的实现的基本运算操作

这些基本操作包括:1.查找单链表中的第i个结点 2.返回指定元素的索引号 3.单链表的插入和删除,这些都是最基本的操作,大家一定要熟练,直接上码图!

* *author:xuzhaohufuncton:单链表的数据结构,查找、插入和删除 */#include <iostream>using namespace std;typedef int datatype;typedef struct node{datatype data;struct node *next;}ListNode,*LinkList;//查找单链表中的第i个节点的地址ListNode * Locate(LinkList head,int i){ListNode *p;int index = 0;if(head==NULL)   return NULL;if(i<0)  return NULL;p = head;if(p!=NULL && index<i){p=p->next;index++;}return p;}//返回链表中第一个节点元素为 x 的值的索引号int findValue(LinkList head,datatype x){ListNode *p;int index = 1;if(head==NULL)  return 0;p=head;if(p!=NULL){if(p->data == x)   return index;else{p=p->next;index++;}}else{cout<<"单链表中没有这个值的元素"<<endl;return 0;}}//单链表的插入 分表首插入和表中插入void InsertNode(LinkList &head,int i,datatype x){ListNode *p,*q;p = Locate(head,i-1);//注意是掺入前面的一个元素q = new ListNode;q->data = x;if(i == 0){q->next = head;head = q;}else{q = p->next;p->next = q;}}//单链表的删除 ,也分表头删除和表中删除void DeleteNode(LinkList &head,int i){ListNode *p,*q;//q是删除节点p是其前一个节点p = Locate(head ,i-1);//注意是找到前面一个元素if(i==0){q = head;head=q->next;delete q;}else{if(p==NULL)//i值给的有问题,索引值溢出后者为负值cout<<"error"<<endl;    else if(p->next == NULL)//p恰好是最后一个元素  cout<<"no ith node"<<endl;else{q = p->next;p->next = q->next;delete q;}}}int main(){return 0;}


二、用链表实现对栈和队列

这是个基础也是非常重要的算法实现问题,既能考察对栈和队列的理解又考察了对链表的操作,因此,也是面试中应该着重学习的地方。

2.1 链栈的数据操作

/** * author:xuzhaohu * function:stackNode 先进后出 * date: */#include<iostream>using namespace std;//与单链表相似,不同之处在与插入和删除只限定在表首进行,表头指针就是栈顶元素 typedef int datatype;typedef struct node{datatype data;struct node *next;}StackNode,*LinkStack;// 链栈的插入 void push(LinkStack &top,datatype x){StackNode *p;p = new StackNode;//注意:链栈的插入只考虑单链表的表首插入 p->data = x;p->next = top;top = p;}//链栈的弹出 void pop(LinkStack &top,datatype &x){StackNode *p;if(top == NULL)cout<<"underflow"<<endl;else{//链栈的删除只考虑表首的删除 p = top;x = top->data;top = top->next;delete p;}}//读取链栈的栈顶元素 void GetTop(LinkStack top, datatype &x){if(top == NULL)  cout<<"error";else  x = top->data;}//置空链栈 void ClearStack(LinkStack &top){top = NULL;}//判断链栈是否为空 int StackEmpty(LinkStack &top){if(top == NULL)  return 1;else  return 0;}int main(){return 0;}

2.2链队列的数据操作

/** *author:xuzh *function:链队列的数据结构,先进先出 */#include<iostream>using namespace std;typedef int datatype;typedef struct node{datatype data;struct node *next;}QueueNode;typedef struct{QueueNode *front;//队头指针QueueNode *rear;//队尾指针}LinkQueue;QueueNode *p,*q;LinkQueue QU;datatype x;//链队列的插入,只能在队尾插入void EnQueue(LinkQueue &QU,datatype x){QueueNode *p = new QueueNode;p->data = x;p->next = NULL;//p为最后一个元素if(QU.front == NULL)  QU.front = QU.rear = p;else{QU.rear->next = p;//队尾元素变为新的元素p,p为旧的队尾元素的下一个元素QU.rear = p;}}//链队列的删除,只能在队首操作void DeQueue(LinkQueue &QU,datatype &x){QueueNode *q;if(QU.front == NULL)  cout<<"underflow"<<endl;else{q = QU.front;x = q->data;QU.front = q->next;delete q;}}//读栈头元素void GetFront(LinkQueue &QU,datatype &x){if(QU.front == NULL)  cout<<"error"<<endl;else  x = QU.front->data;}//清空队列void ClearQueue(LinkQueue &QU){QU.front = QU.rear = NULL;}//判定一个队列是否为空int QueueEmpty(LinkQueue &QU){if(QU.front == NULL)  return 1;else  return 0;}int main(){return 0;}

三、链表的综合操作

3.1.逆向链表的实现 ReverseList(ListNode &head)

思路:通过改变指针指向实现,代码如下:

//reverse the listvoid ReverseList(LinkList &head){ListNode *p,*q,*r;//相邻的两个指针if(head == NULL)  cout<<"error"<<endl;p = head;q = p->next;p->next = NULL;//首节点对应着 逆转后的 最后一个节点while(q!=NULL){if(q->next==NULL)head = q;//表首指针指向最后一个元素r = q->next;q->next = p;p = q;q = r;}}

3.2尽量用最小的时间消耗完成对中间结点的查询

思路:用快慢指针实现,快指针步长为2,慢指针步长为1,同时遍历,当快指针遍历完成时,慢指针指向就是中间结点,复杂度为O(n),代码如下

//尽量用最小的时间消耗完成对中间结点的查询void FindMindleNode(LinkList head){ListNode *fast,*slow;//定义一个快慢指针,一个步长为2,一个步长为1int index = 1;//中间节点的索引值if(head == NULL){  cout<<"error"<<endl;  return ;}slow = fast = head;if(head->next == NULL){  cout<<"the mid node is "<<index<<endl;  return ;} slow = slow->next;//慢指针走一步fast = fast->next;//快指针走两步while(fast!=NULL && slow!=NULL){index++;slow = slow->next;if(fast->next == NULL)//如果fast的下一个指针就已经为空了,说明快指针已经遍历完成fast = fast->next;//这时候让fast指针指向最后一个元素就行elsefast = fast->next->next;}cout<<"the mid node is "<<index<<endl;}

3.3用最小空间和时间找出该链表的倒数第m个元素

思路:同样可以采用双指针的,一个指针先遍历m个元素,然后第二个指针与第一个指针同时遍历链表,直到第一个指针遍历结束,则第二个指针遍历到的位置即为倒数第m个元素。代码如下

//get the mth element in the listint GetLastM(LinkList head, int m){ListNode *fast,*slow;int i = 0;fast = slow = head;if(head == NULL)  return -1;while(fast!=NULL){i++;fast = fast->next;if(i>=m){if(slow!=NULL)  slow = slow->next;}}return slow->data;}

3.4如何判断一个单向链表是否存在循环

思路:一、使用步长法判断,用不同步长遍历两次,看是否遇见。遇见则证明有环;二、如果链表中当前的node的下一个node不是前面的任何一个访问后的node,那么则没有环,否则有环。下面是思路一的代码

// Circle List 's last element->next = headbool CircleList(LinkList head){//define two different step pointer,if they meet,there is circle, or notListNode *fast,*slow;if(head == NULL)  return false;    slow = head;fast = head->next;while(fast!=NULL && fast->next!=NULL && slow!=fast){slow = slow->next;fast = fast->next->next;}if(slow = fast)  return true;else  return 0;}

结束!小弟也在学习中,希望通过自己学到的和大家进行交流,如有不准确和正确的地方,请各位指正,小弟不甚感激!!^_^



原创粉丝点击