关于链表

/*堆排序(大顶堆) 2011.9.14*/

#include <iostream>

#include<algorithm>

using namespace std;

void HeapAdjust(int *a,int i,int size)  //调整堆

{

int lchild=2*i;       //i的左孩子节点序号

int rchild=2*i+1;     //i的右孩子节点序号

int max=i;            //临时变量

if(i<=size/2)          //如果i是叶节点就不用进行调整

{

if(lchild<=size&&a[lchild]>a[max])

{

max=lchild;

}

if(rchild<=size&&a[rchild]>a[max])

{

max=rchild;

}

if(max!=i)

{

swap(a[i],a[max]);

HeapAdjust(a,max,size);    //避免调整之后以max为父节点的子树不是堆

}

}

}

void BuildHeap(int *a,int size)    //建立堆

{

int i;

for(i=size/2;i>=1;i--)    //非叶节点最大序号值为size/2

{

HeapAdjust(a,i,size);

}

}

void HeapSort(int *a,int size)    //堆排序

{

int i;

BuildHeap(a,size);

for(i=size;i>=1;i--)

{

//cout<<a[1]<<" ";

swap(a[1],a[i]);           //交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面

//BuildHeap(a,i-1);        //将余下元素重新建立为大顶堆

HeapAdjust(a,1,i-1);      //重新调整堆顶节点成为大顶堆

}

}

int main(int argc, char *argv[])

{

//int a[]={0,16,20,3,11,17,8};

int a[100];

int size;

while(scanf("%d",&size)==1&&size>0)

{

int i;

for(i=1;i<=size;i++)

cin>>a[i];

HeapSort(a,size);

for(i=1;i<=size;i++)

cout<<a[i]<<"";

cout<<endl;

}

return 0;

}

冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

void bubbleSort(int *a,int len)

{

int i,j,change;

for(i=0;i<len;i++)

{

change=0;

for(j=len-1;j>i;j--)

{

if(a[j]<a[j-1])

{

change=1;

swap(&a[j],&a[j-1]);

}

}

if(!change)

break;

}

}

归并排序

void merge(int *a,int start,int mid,int end)

{

if(start>mid || mid >end ) return;

int i=start,j=mid+1,k=0;

int *L=(int *)malloc((end-start+1)*sizeof(int));

while(i<=mid && j<=end)

{

if(a[i]<a[j])

{

L[k++]=a[i++];

}else          {

L[k++]=a[j++];

}

}

while(i<=mid)

L[k++]=a[i++];

while(j<=end)

L[k++]=a[j++];

for(i=start,j=0;i<=end;i++,j++)

{

a[i]=L[j];

}

free(L);

}

void mergeSort(int *a, int start,int end)

{

if(start<end)

{

int mid=(start+end)/2;

mergeSort(a,start,mid);

mergeSort(a,mid+1,end);

merge(a,start,mid,end);

}

}

/*************************************************************************

这是一个二叉查找树,实现了以下操作:插入结点、构造二叉树、删除结点、查找、

查找最大值、查找最小值、查找指定结点的前驱和后继。上述所有操作时间复杂度

均为o(h),其中h是树的高度

注释很详细,具体内容就看代码吧

*************************************************************************/

#include<stdio.h>

#include<stdlib.h>

//二叉查找树结点描述

typedef int KeyType;

typedef struct Node

{

KeyType key;          //关键字

struct Node * left;   //左孩子指针

struct Node * right;  //右孩子指针

struct Node * parent; //指向父节点指针

}Node,*PNode;

//往二叉查找树中插入结点

//插入的话,可能要改变根结点的地址,所以传的是二级指针

void inseart(PNode * root,KeyType key)

{

//初始化插入结点

PNode p=(PNode)malloc(sizeof(Node));

p->key=key;

p->left=p->right=p->parent=NULL;

//空树时,直接作为根结点

if((*root)==NULL){

*root=p;

return;

}

//插入到当前结点(*root)的左孩子

if((*root)->left == NULL && (*root)->key > key){

p->parent=(*root);

(*root)->left=p;

return;

}

//插入到当前结点(*root)的右孩子

if((*root)->right == NULL && (*root)->key < key){

p->parent=(*root);

(*root)->right=p;

return;

}

if((*root)->key > key)

inseart(&(*root)->left,key);

else if((*root)->key < key)

inseart(&(*root)->right,key);

else

return;

}

//查找元素,找到返回关键字的结点指针,没找到返回NULL

PNode search(PNode root,KeyType key)

{

if(root == NULL)

return NULL;

if(key > root->key) //查找右子树

return search(root->right,key);

else if(key < root->key) //查找左子树

return search(root->left,key);

else

return root;

}

//查找最小关键字,空树时返回NULL

PNode searchMin(PNode root)

{

if(root == NULL)

return NULL;

if(root->left == NULL)

return root;

else  //一直往左孩子找,直到没有左孩子的结点

return searchMin(root->left);

}

//查找最大关键字,空树时返回NULL

PNode searchMax(PNode root)

{

if(root == NULL)

return NULL;

if(root->right == NULL)

return root;

else  //一直往右孩子找,直到没有右孩子的结点

return searchMax(root->right);

}

//查找某个结点的前驱

PNode searchPredecessor(PNode p)

{

//空树

if(p==NULL)

return p;

//有左子树、左子树中最大的那个

if(p->left)

return searchMax(p->left);

//无左子树,查找某个结点的右子树遍历完了

else{

if(p->parent == NULL)

return NULL;

//向上寻找前驱

while(p){

if(p->parent->right == p)

break;

p=p->parent;

}

return p->parent;

}

}

//查找某个结点的后继

PNode searchSuccessor(PNode p)

{

//空树

if(p==NULL)

return p;

//有右子树、右子树中最小的那个

if(p->right)

return searchMin(p->right);

//无右子树,查找某个结点的左子树遍历完了

else{

if(p->parent == NULL)

return NULL;

//向上寻找后继

while(p){

if(p->parent->left == p)

break;

p=p->parent;

}

return p->parent;

}

}

//根据关键字删除某个结点,删除成功返回1,否则返回0

//如果把根结点删掉,那么要改变根结点的地址,所以传二级指针

int deleteNode(PNode* root,KeyType key)

{

PNode q;

//查找到要删除的结点

PNode p=search(*root,key);

KeyType temp;    //暂存后继结点的值

//没查到此关键字

if(!p)

return 0;

//1.被删结点是叶子结点,直接删除

if(p->left == NULL && p->right == NULL){

//只有一个元素,删完之后变成一颗空树

if(p->parent == NULL){

free(p);

(*root)=NULL;

}else{

//删除的结点是父节点的左孩子

if(p->parent->left == p)

p->parent->left=NULL;

else  //删除的结点是父节点的右孩子

p->parent->right=NULL;

free(p);

}

}

//2.被删结点只有左子树

else if(p->left && !(p->right)){

p->left->parent=p->parent;

//如果删除是父结点,要改变父节点指针

if(p->parent == NULL)

*root=p->left;

//删除的结点是父节点的左孩子

else if(p->parent->left == p)

p->parent->left=p->left;

else //删除的结点是父节点的右孩子

p->parent->right=p->left;

free(p);

}

//3.被删结点只有右孩子

else if(p->right && !(p->left)){

p->right->parent=p->parent;

//如果删除是父结点,要改变父节点指针

if(p->parent == NULL)

*root=p->right;

//删除的结点是父节点的左孩子

else if(p->parent->left == p)

p->parent->left=p->right;

else //删除的结点是父节点的右孩子

p->parent->right=p->right;

free(p);

}

//4.被删除的结点既有左孩子,又有右孩子

//该结点的后继结点肯定无左子树(参考上面查找后继结点函数)

//删掉后继结点,后继结点的值代替该结点

else{

//找到要删除结点的后继

q=searchSuccessor(p);

temp=q->key;

//删除后继结点

deleteNode(root,q->key);

p->key=temp;

}

return 1;

}

//创建一棵二叉查找树

void create(PNode* root,KeyType *keyArray,int length)

{

int i;

//逐个结点插入二叉树中

for(i=0;i<length;i++)

inseart(root,keyArray[i]);

}

int main(void)

{

int i;

PNode root=NULL;

KeyType nodeArray[11]={15,6,18,3,7,17,20,2,4,13,9};

create(&root,nodeArray,11);

for(i=0;i<2;i++)

deleteNode(&root,nodeArray[i]);

printf("%d\n",searchPredecessor(root)->key);

printf("%d\n",searchSuccessor(root)->key);

printf("%d\n",searchMin(root)->key);

printf("%d\n",searchMax(root)->key);

printf("%d\n",search(root,13)->key);

return 0;

}

某本书上面说了,链表这个东西,实际用的并不多,但是可以提供很好的考察面试者编程技巧和思维能力的素材。这里总结一下,见过的面试题和对应的候选解法。

题一、 给定单链表,检测是否有环。

使用两个指针p1,p2从链表头开始遍历,p1每次前进一步,p2每次前进两步。如果p2到达链表尾部,说明无环,否则p1、p2必然会在某个时刻相遇(p1==p2),从而检测到链表中有环。

题二、 给定两个单链表(head1, head2),检测两个链表是否有交点,如果有返回第一个交点。

如果head1==head2,那么显然相交,直接返回head1。

否则,分别从head1,head2开始遍历两个链表获得其长度len1与len2。假设len1>=len2,那么指针p1由head1开始向后 移动len1-len2步。指针p2=head2,下面p1、p2每次向后前进一步并比较p1p2是否相等,如果相等即返回该结点,否则说明两个链表没有 交点。

题三、 给定单链表(head),如果有环的话请返回从头结点进入环的第一个节点。

运用题一,我们可以检查链表中是否有环。

如果有环,那么p1p2重合点p必然在环中。从p点断开环,方法为:p1=p, p2=p->next, p->next=NULL。此时,原单链表可以看作两条单链表,一条从head开始,另一条从p2开始,于是运用题二的方法,我们找到它们的第一个交点即为所求。

题四、只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点。

办法很简单,首先是放p中数据,然后将p->next的数据copy入p中,接下来删除p->next即可。

题五、只给定单链表中某个结点p(非空结点),在p前面插入一个结点。

办法与前者类似,首先分配一个结点q,将q插入在p后,接下来将p中的数据copy入q中,然后再将要插入的数据记录在p中。

题六、给定单链表头结点,删除链表中倒数第k个结点。

使用两个节点p1,p2,p1初始化指向头结点,p2一直指向p1后第k个节点,两个结点平行向后移动直到p2到达链表尾部(NULL),然后根据p1删除对应结点。

题八、倒转单链表

给出非递归和递归解法:

#include <iostream>

using namespace std;

struct Node

{

int data;

Node* next;

}*head;

// 非递归写法

Node* InverseLinkedList(Node* head)

{

if(head == NULL)

return NULL;

Node* newHead = NULL;

while(head != NULL)

{

Node* nextNode = head->next;

head->next = newHead;

newHead = head;

head = nextNode;

}

return newHead;

}

// 递归写法

Node* InverseLinkedListRecur(Node* head)

{

if(head == NULL)

return NULL;

if(head->next == NULL)

return head;

Node* newHead = InverseLinkedListRecur(head->next);

Node *tmp = newHead;

while(tmp->next != NULL)

{

tmp = tmp->next;

}

tmp->next = head;

head->next = NULL;

return newHead;

}

int main()

{

// 构建链表 9->8->7->6->5->4->3->2->1->0->NULL

for(int i = 0; i < 10; i++)

{

Node* node = new Node();

node->data = i;

node->next = head;

head = node;

}

head = InverseLinkedList(head);

Node *tmp = head;

while(head != NULL)

{

cout << head->data << " ";

head = head->next;

}

cout << endl;

head = InverseLinkedListRecur(tmp);

tmp = head;

while(head != NULL)

{

cout << head->data << " ";

head = head->next;

}

cout << endl;

while(tmp != NULL)

{

Node* next = tmp->next;

delete tmp;

tmp = next;

}

}

题九、两个有序链表的合并

有两个有序链表,各自内部是有序的,但是两个链表之间是无序的

typedef struct node{

int data;

struct node * next;

}* List;

List mergeSortedLinkList(List list1, List list2)

{

List pList1,pList2,mergedList,pCurNode;

if (list1 == NULL)

{

return list2;

}

if (list2 == NULL)

{

return list1;

}

pList1 = list1;

pList2 = list2;

mergedList = NULL;

if (pList1==pList2)

{

mergedList = pList1;

pList1 = pList1->next;

pList2 = pList2->next;

}

else

{

if (pList1->data <= pList2->data)

{

mergedList = pList1;

pList1 = pList1->next;

}

else

{

mergedList = pList2;

pList2 = pList2->next;

}

}

pCurNode = mergedList;

while(pList1 && pList2)

{

if (pList1==pList2)

{

pCurNode->next = pList1;

pCurNode = pList1;

pList1 = pList1->next;

pList2 = pList2->next;

}

else

{

if (pList1->data <= pList2->data)

{

pCurNode->next = pList1;

pCurNode = pList1;

pList1 = pList1->next;

}

else

{

pCurNode->next = pList2;

pCurNode = pList2;

pList2 = pList2->next;

}

}

}

pCurNode->next =pList1?pList1:pList2;

return mergedList;

}

题十、找出链表的中间元素

单链表的一个比较大的特点用一句广告语来说就是“不走回头路”,不能实现随机存取(random access)。如果我们想要找一个数组a的中间元素,直接a[len/2]就可以了,但是链表不行,因为只有a[len/2 - 1] 知道a[len/2]在哪儿,其他人不知道。因此,如果按照数组的做法依样画葫芦,要找到链表的中点,我们需要做两步(1)知道链表有多长(2)从头结点开始顺序遍历到链表长度的一半的位置。这就需要1.5n(n为链表的长度)的时间复杂度了。有没有更好的办法呢?有的。想法很简单:两个人赛跑,如果A的速度是B的两倍的话,当A到终点的时候,B应该刚到中点。这只需要遍历一遍链表就行了,还不用计算链表的长度。

面试的时候,书写程序要注意以下几点

1.确认了解题意,如果对题意了解不清,应该向面试人员问清楚
2.明确题意后,首先思考找到一个复杂度可以接受的正确算法,并表述出来,注意可以在草稿纸上写写划划,进行验证
3.观察复杂度能否再次降低
4.书写程序时,一定要认真,坚决防止出现逻辑错误,并根据程序具体分析可能的极端情况,处理好边界,并自己进行用例测试,以验证程序。

节点的定义如下:
typedef struct list {
int key;
struct list *next;
}list;

(1)已知链表的头结点head,写一个函数把这个链表逆序 ( Intel)

list * reverse(list * head){
list * h = head;
list * new_head = NULL,*temp;
if(h==NULL) return h;//如果是空链表
do{
   temp = h;
   h = h -> next;
   temp -> next = new_head;
   new_head = temp;
}while(h != NULL && h != head)//检测是循环链表
return new_head;
}

其实还要注意一点,链表内部是否包含小环。

(2)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序。(保留所有结点,即便大小

相同)

list * merge (list *list1_head, list *list2_head){

}

其实需要问一下,head1 head2是否都是从大到小,这点一定要明确,不能默认两个是相同的规格排序。

(3)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序,这次要求用递归方法进行。 (Autodesk)

list * merge (list *list1_head, list *list2_head){
   list * res;
   if(list1_head == NULL) return list2_head;
   if(list2_head == NULL) return list1_head; 
   if(list1_head->key > list2_head->key){
      res = list1_head;
      res->next = merge(list1_head->next,list2_head);

}else{
      res = list2_head;
      res->next = merge(list1_head,list2_head->next);

}
   return res;
}

(4)写一个程序,计算链表的长度

unsigned int list_len(list *head){
  unsigned int len = 0;
  list * h = head;
  if(h == NULL)return 0;
  d0{
      len++;
      h = h->next;
  }while(h != NULL && h != head)  
  return len;
}

如果是循环链表?

(5)有一个链表L,其每个节点有2个指针,一个指针next指向链表的下个节点,另一个random随机指向链表中的任一个节点,可能是自己或者为空,写一个程序,要求复制这个链表的结构并分析其复杂性。

这个题目的思路很巧妙。当然简单的方法,可以利用一个map或者hash,将链表的random指针的指向,保存起来。这样有O(n)存储空间的浪费,时间基本可以接近O(n).

实际上可以这样来做:我们将新的链表节点,插入到原来链表节点当中,并且修改原来的链表的random指针,使得该指针由我们现在的新节点所有。实际上形成下面这样一种结构,同时将原来o的random指针的值,复制给它后面的现在的@的random指针,该结构如下:

o->@->o->@->o->@->NULL

现在可以利用@拥有的random指针方便的找到它真正的random指针。因为原来的@的random指针指向o的random指针,只要把让@->[email protected]>random->next,random就是真正的那个指针了,然后我们再把@从这个链表中删除。

(6)给你一个单向链表的头指针,可能最后不是NULL终止,而是循环链表。题目问你怎么找出这个链表循环部分的第一个节点。

1。如果允许修改节点的数据结构的话,那么就在每个节点上设置一个标志位表示是否被访问过。这样遍历时遇到已访问节点即是循环的第一个节点。
2。如果不允许修改节点,那么就在外部用一个hashmap记录下所有的已访问节点。遍历时先查找这个hashmap,节点不存在则加入,已存在则该节点就是循环的第一个节点。

(7)(华为面试题)找出单向链表中中间结点
这个可以借鉴上面的方法,利用步长为1,步长为2指针,当步长为2的指针到达末尾时,指针1就刚好达到中间。

(8)题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。链表结点的定义如下:
struct ListNode
{
      int        m_nKey;
      ListNode*  m_pNext;
};
函数的声明如下:
void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted);

实际上我们可以将该节点的下一个节点的值copy到这个节点,然后删除下一个节点。这样实际上就将一个不能处理的情况,通过一种技巧转化成了我们可以处理的正常情况。

但是还需要注意下面这个边界情况:如果要删除的是尾指针?这就意味着当前节点没有下一个节点。
通过这点我们也可以发现,如果认真考虑各个因素,思路清晰,还是很容易发现各种异常情况的,保证严谨的思考。

时间: 2024-07-28 16:32:31

关于链表的相关文章

c语言动态链表的创建

创建动态连链表就是将一个个节点连接起来 (1)动态生成节点 (2)输入节点数据 (3)将节点链在一起 例: typedef struct Data { char num[20]; char name[10]; char sex; float english; float chinese; float math; }; typedef struct Node { struct Data data;//结构体类型//结构体嵌套 struct Node* next;//结构体指针型 }node,*Pn

算法学习——单链表快排

/**  * 以p为轴对start-end间的节点进行快排(包括start && 不包括end):  * 思路:  * 1.将头节点作为轴节点start,从start.next开始遍历,如果节点小于轴start的值,将该节点插入到轴节点后面:  * 2.将轴节点插入合适位置,即找到最后一个小于轴的节点,将该节点与轴节点值互换,此时就链表分为两部分,小于轴节点和大于轴节点:  * 3.递归的遍历2中两部分节点.  *   * @param p  * @param start  * @para

【数据结构】之散列链表(Java语言描述)

散列链表,在JDK中的API实现是 HashMap 类. 为什么HashMap被称为"散列链表"?这与HashMap的内部存储结构有关.下面将根据源码进行分析. 首先要说的是,HashMap中维护着的是一个数组: transient Node<K,V>[] table; ,数组中的每个元素都是一个 Node 对象.这里的Node是HashMap的一个内部类,代码如下: static class Node<K,V> implements Map.Entry<

单链表逆置

重写单链表逆置,熟能生巧- #include <iostream> #include <cstdlib> using namespace std; typedef struct List{ int num; struct List *next; }ListNode,*pListNode; void display(ListNode *pHead) { while(pHead) { cout<<pHead->num<<"--"; pH

java-------单链表

单链表: * 1.链表可以是一种有序或无序的列表 * 2.链表的内容通常存储在内存中分散的为止 * 3.链表由节点组成,每一个节点具有相同的结构 * 4.节点分为数据域和链域,数据域存放节点内容,链域存放下一个节点的指针 package myLinkList; public class MyLinkedList<T> { /** *Node:节点对象 * 包括数据域data和链域next(指向下一个节点对象) */ class Node { private T data; private No

数据结构之链表

---恢复内容开始--- 1:有头节点.单向.不循环链表 对于这种有头节点的单向不循环链表插入数据如下图: 1)头部插入 2)尾部插入 代码如下 : 1 #include "stdafx.h" 2 #include<iostream> 3 4 using namespace std; 5 6 typedef int DATA; 7 typedef struct node* LIST; 8 9 struct node 10 { 11 DATA data; 12 struct

拿java写了一个有点像指针的单链表

public class LinkList { private Node firstNode; private Integer position; public LinkList() {  super(); } public LinkList(Node firstNode) {  super();  this.firstNode = firstNode; } public Node getFirstNode() {  return firstNode; } public void setFirs

02 单链表

线性表之链式存储---单链表 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 // 数据结构 6 typedef struct node 7 { 8 int data; 9 struct node *next; 10 }linkList; 11 12 // 创建单链表,并初始化 13 linkList *linkList_init(void) 14 { 15 linkList *l

(单链表)单链表的整体逆序和局部逆序

题目一:将单链表翻转. 思路:有三种方式. 一:用数组存储单链表的值,然后重新逆序赋值,效率较低. 二:利用三个指针,在原来的基础上进行逆序.这种方法比较实用,效率也高. 三:从第2个节点到第N个节点,依次逐节点插入到第1个节点(head节点)之后,最后将第一个节点挪到新表的表尾.需要新建一个链表,这种方法和第二种差不多. 这里我就写出第二种方法,比较实用. 代码(方法二): struct ListNode { int val; ListNode *next; ListNode(int x) :

链表操作法则之逆向遍历与倒置算法

一.创建链表: 对链表进行操作的所有算法的前提,就是我们首先要创建一个链表,我们可以选择正向建链和逆向建链: (一).正向建链: 首先,我们得自定义节点类型: typedef struct Node { int data;//数据域 struct Node * pNext;//指针域 }NODE,*PNODE; 通过数组进行链表数据域的赋值: int main (void) { PNODE pHead;//头指针,接收创建链表时返回的头结点地址 int a[8] = {12,37,49,65,2