一、线性表的定义 具有相同数据类型的 n 个数据元素的有限序列
线性表是一种逻辑结构,表示元素之间一对一的逻辑关系。
顺序表和链表是存储结构。
二、顺序表 1. 顺序表定义: 线性表的顺序存储
1 2 3 4 typedef struct { int data[50 ]; int length; } SqList;
逻辑上相邻的两个元素在物理位置上也相邻
特点:
随机访问(通过首地址和元素序号可在时间O(1)内找到元素)
插入和删除需要移动大量元素
存储密度高,每个结点只存储数据元素
2. 顺序表基本操作 ① 插入 在第i个位置(下标i-1)插入元素e
1 2 3 4 5 for (int j = length; j >= i; j--) L[j] = L[j - 1 ]; L[i - 1 ] = e; length++;
最好情况:在表尾插入,时间复杂度 O(1)
最坏情况:在表头插入,时间复杂度 O(n)
平均情况:时间复杂度 O(n)
② 删除 删除第i个位置的元素,用e返回
1 2 3 4 5 e = L[i-1 ]; for (int j = i; j<length;j++) L[j-1 ] = L[j]; length --;
最好情况:删除表尾元素,O(1)
最坏情况:删除表头元素,O(n)
平均情况:O(n)
③ 按值查找 1 2 3 for (int i = 0 ; i < length; i++) if (L[i] == e) return i;
最好情况:查找元素在表头,O(1)
最坏情况:查找元素在表尾,O(n)
平均情况:O(n)
3. 顺序表相关算法 ① 删除线性表中所有值为x的数据元素
1 2 3 4 5 6 7 8 9 10 11 12 13 void delete_x (int &a, int x) { int i = 0 ; int k = 0 ; while (i<len){ if (a[i]==x) k++; else a[i-k] = a[i]; i++; } len = len - k; }
② 将两个有序顺序表合成一个有序顺序表 归并思想
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 typedef struct { int data[50 ]; int length; } SqList; bool merge (SqList a,SqList b,SqList &c) { if (a.length+b.length>50 ) return false ; int i=0 ,j=0 ,k=0 ; while (i<a.length && j<b.length){ if (a.data[i]<b.data[j]){ c.data[k++] = a.data[i++]; } else c.data[k++] = b.data[j++]; } while (i<a.length){ c.data[k++] = a.data[i++]; } while (j<b.length){ c.data[k++] = b.data[j++]; } c.length = k; return true ; }
③ 将数组R中的n个元素循环左移p个位置 题目等同于将ab转变为ba(a为前p个元素)
XY —> (XTYT)T = YX
1 2 3 reverse(R,0 ,n-1 ); reverse(R,0 ,p-1 ); reverse(R,p,n-1 );
④ 找出两个等长升序序列A,B的中位数(取上整) A中位数a,B中位数b
若a=b: 返回a或b
若a>b: 舍弃a右边较大的部分,同时舍弃b左边较小的部分(不删除中位数)
若a<b:舍弃a左边较小的部分,同时舍弃b右边较大的部分
重复上述操作,直到两序列都只剩一个元素,较小值即为中位数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 int Mid (int A[], int B[], int n) { int s1 = 0 , d1 = n - 1 , m1; int s2 = 0 , d2 = n - 1 , m2; while (s1 != d1 || s2 != d2) { m1 = (s1 + d1) / 2 ; m2 = (s2 + d2) / 2 ; if (A[m1] == A[m2]) return A[m1]; else if (A[m1] > A[m2]) { if ((s1 + d1) % 2 == 0 ) { d1 = m1; s2 = m2; } else { d1 = m1; s2 = m2 + 1 ; } } else { if ((s1 + d1) % 2 == 0 ) { d2 = m2; s1 = m1; } else { d2 = m2; s1 = m1 + 1 ; } } } return A[s1]<A[s2]?A[s1]:A[s2]; }
三、链表 链表分为:
单链表
双链表
循环单链表(判空条件:表尾结点的next是否是等于头指针)
循环双链表
1. 单链表 ① 定义 1 2 3 4 typedef struct LNode { ElemType data; struct LNode * next ; }LNode,*LinkList;
单链表可以解决顺序表需要大量连续存储空间的缺点,但单链表附加指针域,也存在浪费存储空间的缺点
单链表是非随机存储的存储结构:即不能直接找到表中某个特点的结点。需要从头开始遍历。
单链表访问前驱的时间复杂度为O(n),访问后继O(1)
引入头节点的优点:
链表的第一个元素位置上的操作与其他位置的元素操作一样,无须进行特殊处理
无论链表是否为空,其头指针都是指向头节点的非空指针,空表和非空表的处理得到了统一
(头指针始终指向第一个结点,有头节点则指向头节点,无头节点则指向第一个元素结点)
② 基本操作 头插法:(读入顺序和生成顺序相反) 1 2 s->next = L->next; L->next = s;
尾插法:(读入顺序和生成顺序相同)
按序号查找 按值查找 插入结点: p之后插入s (p的位置需要自行查找)
1 2 s->next = p->next; p->next = s;
删除结点: 1 2 3 4 q = p->next; p->next = q->next; free (q);
求表长 2. 双链表 ① 定义 就是同时具有前驱指针和后继指针的链表
访问前驱和后继结点时间复杂度都是O(1)
1 2 3 4 typedef struct DNode { int data; struct DNode * next ,*prior ; }DNode,*DinkList;
② 基本操作 插入: p之后插入s
1 2 3 4 s->next = p->next; p->next->prior = s; p->next = s; s->prior = p;
删除: 1 2 3 4 p->next = s->next; s->next->prior = p; free (s);
3. 循环单链表
判空条件:表尾结点的next是否是等于头指针
4. 循环双链表
四、顺序表和链表的比较
五、链表的相关算法 1. 递归删除不带头节点的单链表L中所有值为x的结点 1 2 3 4 5 6 7 8 9 10 11 12 13 void Del (LinkList &L,int x) { LNode *p; if (L==NULL ) return ; else if (L->data == x){ p=L; L=L->next; free (p); Del(L,x); } else Del(L->next,x); }
2. 反向输出单链表中每个结点的值 1 2 3 4 5 void rprint (LinkList L) { if (L->next!=NULL ) rprint(L->next); cout <<L->data<<" " ; }
3. 带头结点的单链表就地逆置 1 2 3 4 5 6 7 8 9 10 11 12 LinkList Reverse (LinkList &L) { LNode *p=L->next, *r=L; L->next = NULL ; while (p){ r = p->next; p->next = L->next; L->next = p; p = r; } return L; }
4. 找出两个链表的公共结点 算法思想:两个链表公共结点之后的所有结点都重合,呈现Y型;
先走完较长链表的多出来的一部分,到达同样长度后再同步比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 LinkList Search_Common (LinkList L1, LinkList L2) { int len1 = L1.length; int len2 = L2.length; int longlist, shortlist; if (len1 > len2) longlist = L1->next; shortlist = L2->next; dist = len1 - len2; else longlist = L2; shortlist = L1; dist = len2 - len1; while (dist--) longlist = longlist->next; while (longlist != Null) { if (longlist == shortlist) return longlist; else { longlist = longlist->next; shortlist = shortlist->next; } } }
5. 在一个递增有序的单链表中,删除数值相同的元素,使表中不再有重复的元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void Del_Same (LinkList &L) { LNode *p=L->next, *r; if (p==NULL ) return ; while (p->next!=NULL ){ r = p->next; if (p->data == r->data){ p->next = r->next; free (r); } else p = p->next; } }
6. 查找链表中倒数第K个位置的结点 算法思想:快慢指针法;
快指针先走k步,随后快慢指针同时依次后移,快指针到达链表尾部时,慢指针正好访问倒数第k个位置结点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int Search_k (LinkList L, int k) { LNode *p = L->next, *q = L->next; int count = 0 ; while (p!=Null){ if (conut < k){ p = p->next; count++; } else { p = p->next; q = q->next; } } if (count > k) return 0 ; else return 1 ; }
7. 查找单链表的中间结点 此题可应用于上一题类似的思想。也是设置两个指针,只不过这里是,两个指针同时向前走,前面的指针每次走两步,后面的指针每次走一步,前面的指针走到最后一个结点时,后面的指针所指结点就是中间结点,即第(n/2+1)个结点。注意链表为空、链表结点个数为1和2的情况。时间复杂度O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ListNode * GetMiddleNode (ListNode * pHead) { if (pHead == NULL || pHead->m_pNext == NULL ) return pHead; ListNode * pAhead = pHead; ListNode * pBehind = pHead; while (pAhead->m_pNext != NULL ) { pAhead = pAhead->m_pNext; pBehind = pBehind->m_pNext; if (pAhead->m_pNext != NULL ) pAhead = pAhead->m_pNext; } return pBehind; }
8. 如何判断单链表是否有环以及环的入口 假设一个单链表有环,他就是下面这种情况:
判断是否有环 :使用二指针法,设定两个指针slow、fast,从头指针开始,每次分别前进1步、2步。
如存在环,则两者相遇;如不存在环,fast遇到NULL退出
1 2 3 4 5 6 7 while (slow != NULL && fast != NULL && fast -> next != NULL ) { slow = slow -> next ; fast = fast -> next -> next ; if (slow == fast) return true ; } return false ;
环的入口 :头结点到入口的距离 = slow 和 fast 指针相遇点到入口的距离。设定一个start 指针指向头结点 ,meet 指针指向slow 和fast 指针相遇处,将start 指针和meet 指针遍历,start 指针和meet 指针相等时,则该地址为环的入口地址 。
1 2 3 4 5 6 7 8 9 start=head; meet=fast; while (start!= meet) { start = start->next; meet = meet->next; } return start;