数据结构之线性表(手把手带你刷OJ)
线性表的定义:零个或多个数据元素的有限序列
线性表在逻辑上是线性结构,就是连续的一条直线。但在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表
顺序表存储结构
是一段地址连续的存储单元依次存储线性表的数据元素。一般采用数组来存储。
顺序表的两种实现方式
1.定长数组
定长数组也就是静态数组,无法动态开辟内存。
缺点
- 无法进行动态开辟内存。也就是当数据元素多时数据开劈的空间不够用,又或者是数据元素少时,又开劈太多的空间造成浪费。
2.动态数组
动态数组是可以动态开劈内存,可以避免定长数组的劣势。动态数组开劈内存时一般选择1.5倍或者2倍。
动态数组的定义
顺序表的相关OJ题
题解思路:采用双指针。两个指针一开始指向同一位置,当等于val值时,一个指针不动,另一个指针去找不等于val的值将其覆盖,并且两个指针同时++;
int removeElement(int* nums, int numsSize, int val) {
if(nums == NULL)
return NULL;
int sub1 = 0;//下标1
int sub2 = 0;//下标2
while(sub2 < numsSize ){
//当下标2等于val值时,sub2++,
if(nums[sub2] == val){
sub2++;
}
//当sub2二不等于val值时,覆盖sub1的val值,并且两个下标同时++
else
{
nums[sub1] = nums[sub2];
sub1++;
sub2++;
}
}
return sub1;
}
解题思路:采用前后指针的思路。一个指针去找需要删除重复项的值,一个指针去找不需要删除重复项的值,先让后指针==再将其覆盖。
int removeDuplicates(int* nums, int numsSize) {
if(nums == NULL)
return nums;
int sub1 = 0;//下标1
int sub2 = 1;//下标2
while(sub2 < numsSize)
{
//当两个指针的值相同时,让前指针去找不等于当前指针的值。
if(nums[sub1] == nums[sub2]){
sub2++;
}else{
//当前指针找到不等于当前指针的值,赋值给后指针将其覆盖。
sub1++;
nums[sub1] = nums[sub2];
}
}
//由于函数调用接口是小于返回值sub1,因此要让sub1++;
sub1++;
return sub1;
}
题解思路:双指针。一个指针指向数组1的有效元素位,另一个指针指向数组2的有效元素位。循环比较两个数组,数值大的尾插到数组1的后面。
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {
//从数组有效元素下标开始
int ptr1 = m-1;
int ptr2 = n-1;
int p = m+n-1;
while( ptr1>=0 && ptr2>=0 )
{
//从数组的有效元素下标开始判断比较谁大,就尾插到nums1中
if( nums1[ptr1] > nums2[ptr2] )
{
nums1[p] = nums1[ptr1];
ptr1--;
p--;
}
else
{
nums1[p] = nums2[ptr2];
ptr2--;
p--;
}
}
//如果有一数组提前结束,将还未完成的数组赋值给nums1
while(ptr2>=0)
{
nums1[p] = nums2[ptr2];
p--; ptr2--;
}
while(ptr1>=0)
{
nums1[p] = nums1[ptr1];
p--; ptr1--;
}
}
链表
单链表
n个结点链结成一个链表,即为线性表的链式存储结构。
结点的组成
结点是由数据域和指针域组成的
数据域:存储数据元素信息的域
指针域:存储后续位置结点的地址
头指针与头结点的异同
头指针:链表中第一个结点的存储位置
头结点:在链表前附设的一个结点
头指针
- 指向第一个结点的指针,若有头结点,则指向头结点
- 具有标志作用
- 无论链表是否为空,头指针不为空。头指针是链表的必要元素
头结点
- 为了操作的统一和方便而设立,放在第一个元素之前,其数据域一般无意义
- 头结点方便头插和头删
- 头结点不一定是链表的必须要素
单链表的定义
typedef int SLDataType;
struct SLNode
{
struct SLNode* _next;
SLDataType _data;
};
单链表存储结构与顺序存储结构的优缺点
存储分配方式
- 顺序存储结构用一段连续存储单元依次存储数据元素
- 单链表采用链式存储结构,用一组任意的存储单元存放数据元素
时间性能
-
查找
- 顺序存储结构 O(1)
- 单链表 O(n)
-
插入和删除
- 顺序存储结构需要移动元素,时间复杂度 O(n)
- 单链表需要找出位置的指针,插入和删除的时间复杂度为O(1)
-
空间性能
- 顺序存储结构需要预分配存储空间,分大了,浪费,分小了,不够用,会造成空间碎片
- 单链表不需要预分配存储空间
链表的分类
单向和双向
带头和不带头
循环和非循环
链表总有八种分类,单向带头循环、单向带头不循环、单向不带头循环、单向不带头不循环;双向带头循环、双向带头不循环、双向不带头循环、双向不带头不循环。
链表的OJ题
解题思路:创建两个指针,一个做头,一个指针做尾。遍历需要移除元素的链表,判断不是val的结点链接到新的链表。
struct ListNode* removeElements(struct ListNode* head, int val) {
if (head == NULL)
return head;
struct ListNode* cur = head;
struct ListNode* prev = NULL;
struct ListNode* tail = NULL;
//cur不为空进入循环
while (cur) {
//判断头是不是空,直接赋值给新的头
if (cur->val != val && prev == NULL) {
tail = prev = cur;
} else if (cur->val != val && prev != NULL) {
tail->next = cur;
tail = cur;
}
cur = cur->next;
}
//最后的next置空
if (tail != NULL )
tail->next = NULL;
return prev;
}
解题思路:创建三个指针,第一个指针置NULL,第二个指针等于头,第三个指针等于头的next。让第二个指针的next指向第一个指针,再让第二个指针来到第三个指针的位置,第三个指针在往后面走。
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
if(head == NULL)
return head;
ListNode* newhead = NULL;
ListNode* cur = head;
ListNode* next= NULL;
while(cur)
{
next=cur->next;
cur->next=newhead;
newhead=cur;
cur=next;
}
return newhead;
}
解题思路:采用快慢指针。快指针走两步,满指针走一步。
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
if(head == NULL)
return NULL;
ListNode* slow = head;
ListNode* tail = head;
while(tail&&tail->next)
{
slow = slow->next;
tail=tail->next->next;
}
return slow;
}
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
if(head == NULL)
return NULL;
ListNode* slow = head;
ListNode* tail = head;
while(tail&&tail->next)
{
slow = slow->next;
tail=tail->next->next;
}
return slow;
}