数据结构-顺序表、链表的相关操作以及相关练习题

顺序表(整体代码)

#include<iostream>
#include<time.h>

using namespace std;

//顺序表的结构定义
typedef struct SqList
{
	int count;//顺序表存储了多少个元素
	int size;//顺序表的大小
	int* data;//顺序表的存储区
}SqList;

//初始化顺序表
SqList* GetNewList(int n)
{
	SqList* p = (SqList*)malloc(sizeof(SqList));//指针大小
	p->size = n;
	p->count = 0;
	p->data = (int*)malloc(sizeof(int) * n);
	return p;//返回指向这个顺序表的指针
}

//销毁顺序表
void clear(SqList* L)
{
	if (L == NULL)
	{
		return;
	}
	free(L->data);
	free(L);
	return;
}

//扩容操作
int expand(SqList* L)
{
	if (L == NULL)
		return 0;
	cout << "发生了扩容操作" << endl;
	//L->data = (int*)realloc(L->data, sizeof(int)*1.5*L->size);//扩容到原来的1.5倍,这种做法可能有bug
	int* p = (int*)realloc(L->data, sizeof(int) * 1.5 * L->size);
	if (NULL == p)
	{
		cout << "扩容失败" << endl;
		return 0;
	}
	L->data = p;//规避bug
	L->size *= 1.5;
	return 1;
}

//插入操作,插入到pos位置上
int insert(SqList* L, int pos, int val)
{
	if (pos < 0 || pos > L->size)
	{
		cout << "插入位置不合法" << endl;
		return 0;
	}
	if (pos > L->count)
	{
		cout << "插入失败,请按顺序插入" << endl;
		return 0;
	}
	if (L->size == L->count && !expand(L))
	{
		cout << "存储空间已满,无法插入" << endl;
		return 0;
	}
	for (int i = L->count - 1; i >= pos; i--)
	{
		L->data[i + 1] = L->data[i];
	}

	L->data[pos] = val;
	L->count += 1;
	return 1;
}



//删除操作
int erase(SqList* L, int pos)
{
	if (pos<0 || pos>L->count)
	{
		cout << "删除位置不合法" << endl;
		return 0;
	}
	if (L->count == 0)
	{
		cout << "存储空间为空,无法删除" << endl;
		return 0;
	}
	for (int i = pos; i < L->count - 1; i++)
	{
		L->data[i] = L->data[i + 1];
	}
	L->count -= 1;
	return 1;
}

//输出操作
void cout_List(SqList* L)
{
	int len = 0; // 用来记录输出的长度,为输出的格式服务
	for (int i = 0; i < L->size; i++)
	{
		len += printf("%3d", i);
	}
	printf("\n");
	for (int i = 0; i < len; i++)
	{
		printf("-");
	}
	printf("\n");
	for (int i = 0; i < L->count; i++)
	{
		printf("%3d", L->data[i]);
	}
	printf("\n");
	return;
}

int main()
{
	srand(time(0));
	SqList* L = GetNewList(2);
	for (int i = 0; i < 10; i++)
	{
		int op = rand() % 5;//定义一个随机数来决定是插入还是删除
		int val, pos = 0;
		switch (op)
		{
		case 0:
		case 1:
		case 2:
		case 3:
			pos = rand() % (L->count + 2);//+2是为了触发非法操作
			val = rand() % 100;
			cout << "插入" << val << "到" << pos << "位置" << endl;
		    insert(L, pos, val);
			break;
		case 4:
			pos = rand() % (L->count + 2);
			cout << "删除" << pos << "位置的数据" << endl;
			erase(L, pos);
			break;
		}
		cout_List(L);
	}
	clear(L);
	return 0;
}

运行结果(不唯一)

删除1位置的数据
删除位置不合法
  0  1
------

插入59到0位置
  0  1
------
 59
删除0位置的数据
  0  1
------

插入24到0位置
  0  1
------
 24
插入22到1位置
  0  1
------
 24 22
插入95到3位置
插入位置不合法
  0  1
------
 24 22
插入11到2位置
发生了扩容操作
  0  1  2
---------
 24 22 11
插入17到0位置
发生了扩容操作
  0  1  2  3
------------
 17 24 22 11
插入32到2位置
发生了扩容操作
  0  1  2  3  4  5
------------------
 17 24 32 22 11
插入53到5位置
  0  1  2  3  4  5
------------------
 17 24 32 22 11 53

插入操作: 

//插入操作,插入到pos位置上
int insert(SqList* L, int pos, int val)
{
	if (pos < 0 || pos > L->size)
	{
		cout << "插入位置不合法" << endl;
		return 0;
	}
	if (pos > L->count)
	{
		cout << "插入失败,请按顺序插入" << endl;
		return 0;
	}
	if (L->size == L->count && !expand(L))
	{
		cout << "存储空间已满,无法插入" << endl;
		return 0;
	}
	for (int i = L->count - 1; i >= pos; i--)
	{
		L->data[i + 1] = L->data[i];
	}

	L->data[pos] = val;
	L->count += 1;
	return 1;
}

删除操作:

//删除操作,删除pos位置上的数据
int erase(SqList* L, int pos)
{
	if (pos<0 || pos>L->count)
	{
		cout << "删除位置不合法" << endl;
		return 0;
	}
	if (L->count == 0)
	{
		cout << "存储空间为空,无法删除" << endl;
		return 0;
	}
	for (int i = pos; i < L->count - 1; i++)
	{
		L->data[i] = L->data[i + 1];
	}
	L->count -= 1;
	return 1;
}

扩容操作:

realloc工作原理

realloc会试着原内存地址的基础上向后进行延展,打个比方:原来的内存大小是4个字节,由于这4个字节大小的内存在整个内存空间中只是一小段,那它后面可能有没被使用的空间,此时realloc的尝试就成功了,那么realloc的返回值与原地址是一样的;


如果后面没有足够的空间进行扩容,那么realloc会重新找一片新的足够扩容的空间,将原来空间中的内容拷贝到新空间里来,再将原空间销毁并返回新空间的地址。


又如果找不到足够用来扩容的空间,那么realloc就会返回NULL,并且原内存不动,此时会出现bug。

//扩容操作
int expand(SqList* L)
{
	if (L == NULL)
		return 0;
	cout << "发生了扩容操作" << endl;
	//L->data = (int*)realloc(L->data, sizeof(int)*1.5*L->size);//扩容到原来的1.5倍,这种做法可能有bug
	int* p = (int*)realloc(L->data, sizeof(int) * 1.5 * L->size);
	if (NULL == p)
	{
		cout << "扩容失败" << endl;
		return 0;
	}
	L->data = p;//规避bug
	L->size *= 1.5;
	return 1;
}

链表(整体代码)

#include<iostream>
#include<time.h>

using namespace std;
//无头链表

//定义链表节点信息
typedef struct ListNode
{
	int data;//数据域
	struct ListNode* next;//指针域
}ListNode;

//初始化链表节点信息
ListNode* GetListNode(int val)
{
	ListNode* p = (ListNode*)malloc(sizeof(ListNode));
	p->data = val;
	p->next = NULL;
	return p;
}

//销毁链表节点
void clear(ListNode* head)
{
	if (NULL == head)  return;
	ListNode* q = NULL;//定义一个临时节点
	for (ListNode* p = head; p; p = q)
	{
		q = p->next;//如果不将p的下一个节点信息传给q而直接free的话会造成内存泄露
		free(p);
	}
}

//插入操作,插入pos前一位并返回新插入链表节点的首地址
ListNode* insert(ListNode* head, int pos, int val)
{
	int len = 0;
	for (ListNode* p = head; p; p = p->next)
	{
		len += 1;
	}
	if (len < pos)  pos = len;//超出链表长度则插入最后一个位置

	ListNode* node = GetListNode(val);
	ListNode new_head, * p = &new_head;//定义一个虚拟头节点,并将这个虚拟头节点的地址传给指针p
	new_head.next = head;
	/*
	定义一个虚拟头节点有个好处:
	如果待插入位置是1,p就向后走1个位置...
	这样的话,逻辑思路就更加清楚
	*/
	for (int i = 0; i < pos; i++)
	{
		p = p->next;
	}
	node->next = p->next;
	p->next = node;
	return new_head.next;
}

//删除操作,删除pos位置的值
ListNode* erase(ListNode* head, int pos) 
{
	if (pos < 0) 
	{
		cout << "删除位置不合法" << endl;
		return head;
	}

	if (head == NULL) 
	{
		cout << "无节点可删除" << endl;
		return head;
	}

	if (pos == 0) 
	{
		ListNode* new_head = head->next; // 新的头节点
		free(head); // 释放原头节点
		return new_head;
	}

	ListNode* current = head;
	int i = 0;
	while (i < pos - 1 && current->next != NULL) 
	{
		current = current->next;
		i++;
	}

	if (i < pos - 1 || current->next == NULL) 
	{
		cout << "删除位置不合法" << endl;
		return head;
	}

	ListNode* temp = current->next;
	current->next = temp->next;
	free(temp);
	return head;
}

//打印操作
void cout_list(ListNode* head)
{
	int len = 0;
	for (ListNode* p = head; p; p = p->next)
	{
		len += 1;
	}
	for (int i = 0; i < len; i++)
	{
		printf("%-4d", i);
		cout << " ";
	}
	cout << endl;
	for (ListNode* p = head; p; p = p->next)
	{
		printf("%-3d", p->data);
		if (p->next != NULL)
		{
			cout << "->";
		}
	}
	cout << endl;
}

//查找操作
int find(ListNode* head, int val)
{
	ListNode* p = head;
	int n = 0;//记录经历了多少个节点
	while (p)
	{
		if (p->data == val)
		{
			cout_list(head);
			int len = n * (3 + 2);//3是一个数据占3位,2是'->'的长度,len代表空格的数量
			for (int i = 0; i < len; i++)
			{
				cout << " ";
			}
			cout << "^" << endl;
			for (int i = 0; i < len; i++)
			{
				cout << " ";
			}
			cout << "|" << endl;
			return 1;
		}
		n += 1;
		p = p->next;
	}
	return 0;
}

int main()
{
	srand(time(0));
	ListNode* head = NULL;
	for (int i = 0; i < 10; i++)
	{
		int op = rand() % 5;//定义一个随机数来决定是插入还是删除
		int val, pos = 0;
		switch (op)
		{
		case 0:
		case 1:
		case 2:
		case 3:
			pos = rand() % (i + 1);
			val = rand() % 100;
			cout << "插入" << val << "到" << pos << "位置" << endl;
			head = insert(head, pos, val);
			break;
		case 4:
			pos = rand() % (i + 1);
			cout << "删除" << pos << "位置的数据" << endl;
			head = erase(head, pos);
			break;
		}
		cout_list(head);
	}
	int n = 0;
	while (cin >> n && n >= 0)
	{
		if (!find(head, n))
		{
			cout<<"查找失败"<<endl;
		}
	}
	clear(head);
	return 0;
}

插入操作:

(无头节点的链表)

//插入操作,插入pos前一位并返回新插入链表节点的首地址
ListNode* insert(ListNode* head, int pos, int val)
{
	int len = 0;
	for (ListNode* p = head; p; p = p->next)
	{
		len += 1;
	}
	if (len < pos)  pos = len;//超出链表长度则插入最后一个位置
	//防止第一个节点改变原来的链表地址
	if (pos == 0)
	{
		ListNode* p = GetListNode(val);
		p->next = head;
		return p;
	}

	ListNode* p = head;
	for (int i = 0; i < pos-1; i++)
	{
		p = p->next;
	}
	ListNode* new_node = GetListNode(val);
	//以下两步不能写反,否则会丢失后续链表的信息,造成内存泄露
	new_node->next = p->next;
	p->next = new_node;
	return head;
}

(有头节点的链表)

//插入操作,插入pos前一位并返回新插入链表节点的首地址
ListNode* insert(ListNode* head, int pos, int val)
{
	int len = 0;
	for (ListNode* p = head; p; p = p->next)
	{
		len += 1;
	}
	if (len < pos)  pos = len;//超出链表长度则插入最后一个位置

	ListNode* node = GetListNode(val);
	ListNode new_head, * p = &new_head;//定义一个虚拟头节点,并将这个虚拟头节点的地址传给指针p
	new_head.next = head;
	/*
	定义一个虚拟头节点有个好处:
	如果待插入位置是1,p就向后走1个位置...
	这样的话,逻辑思路就更加清楚
	*/
	for (int i = 0; i < pos; i++)
	{
		p = p->next;
	}
	node->next = p->next;
	p->next = node;
	return new_head.next;
}

删除操作:

//删除操作,删除pos位置的值
ListNode* erase(ListNode* head, int pos) 
{
	if (pos < 0) 
	{
		cout << "删除位置不合法" << endl;
		return head;
	}

	if (head == NULL) 
	{
		cout << "无节点可删除" << endl;
		return head;
	}

	if (pos == 0) 
	{
		ListNode* new_head = head->next; // 新的头节点
		free(head); // 释放原头节点
		return new_head;
	}

	ListNode* current = head;
	int i = 0;
	while (i < pos - 1 && current->next != NULL) 
	{
		current = current->next;
		i++;
	}

	if (i < pos - 1 || current->next == NULL) 
	{
		cout << "删除位置不合法" << endl;
		return head;
	}

	ListNode* temp = current->next;
	current->next = temp->next;
	free(temp);
	return head;
}

查找操作:

//查找操作
int find(ListNode* head, int val)
{
	ListNode* p = head;
	int n = 0;//记录经历了多少个节点
	while (p)
	{
		if (p->data == val)
		{
			cout_list(head);
			int len = n * (3 + 2);//3是一个数据占3位,2是'->'的长度,len代表空格的数量
			for (int i = 0; i < len; i++)
			{
				cout << " ";
			}
			cout << "^" << endl;
			for (int i = 0; i < len; i++)
			{
				cout << " ";
			}
			cout << "|" << endl;
			return 1;
		}
		n += 1;
		p = p->next;
	}
	return 0;
}

小练习

反转链表(Leetcode-206):

给你单链表的头节点head,请你反转链表,并返回反转后的链表。

(非递归做法)

 struct ListNode 
 {
     int val;
     ListNode *next;
     ListNode(int x) : val(x), next(NULL) {}
 };

class Solution 
{
public:
    ListNode* reverseList(ListNode* head) 
    {
        ListNode* p = head;
        ListNode new_head, * q;//定义虚拟头节点
        new_head.next = NULL;
        //采用头插法
        while (p)
        {
            q = p->next;//p就相当于一个标记
            p->next = new_head.next;
            new_head.next = p;
            p = q;
        }
        return new_head.next;
    }
};

(递归做法)

 struct ListNode 
 {
     int val;
     ListNode *next;
     ListNode(int x) : val(x), next(NULL) {}
 };

class Solution 
{
public:
    ListNode* reverseList(ListNode* head) 
    {
        //原链表为空或者只有一个节点就不用处理
        if (head == NULL || head->next == NULL)
        {
            return head;
        }

        //对于头节点以外部分的链表,反转后的尾节点就是头节点的下一个节点
        ListNode* tail = head->next;

        //处理头节点以外部分的链表,new_head就是反转链表的地址
        ListNode* new_head = reverseList(head->next);

        //处理头节点,尾插法
        head->next = tail->next;
        tail->next = head;
        return new_head;
    }
};

环形链表(Leetcode-141):

给你一个链表的头节点 head ,判断链表中是否有环。

 struct ListNode 
 {
     int val;
     ListNode *next;
     ListNode(int x) : val(x), next(NULL) {}
 };

class Solution 
{
public:
    /*
    * 就像操场上跑圈,跑得快的一定会追上跑得慢的,除非没有环
    */
    bool hasCycle(ListNode* head) 
    {
        //快慢指针
        ListNode* p = head, * q = head;
        while (q && q->next)
        {
            p = p->next;//p每次跑一步
            q = q->next;//q每次跑两步
            if (p == q)
                return true;
        }
        return false;
    }
};

快乐数(Leetcode-202):

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

这个变化过程就像是链表:

如果把1看作空地址NULL,那么问题就变成了:从链表的一个节点开始,究竟能不能到达空地址。这就和环形链表很像了:如果它不是快乐数,那么它就会形成一个环,无法到达空地址。所以采用“快慢指针”的做法。

class Solution 
{
public:
    int getnext(int x)
    {
        //y代表变化后的数字
        int d, y = 0;
        while (x)
        {
            d = x % 10;//d代表个位数
            y += d * d;
            x /= 10;
        }
        return y;
    }

    bool isHappy(int n) 
    {
        int p = n, q = n;
        while (p != 1)
        {
            p = getnext(p);
            q = getnext(getnext(q));

            //形成了‘环’且没到达终点
            if (p == q && p != 1)
                return false;
        }
        return true;
    }
};

 旋转链表(Leetcode-61):

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

 struct ListNode 
 {
     int val;
     ListNode *next;
     ListNode() : val(0), next(nullptr) {}
     ListNode(int x) : val(x), next(nullptr) {}
     ListNode(int x, ListNode *next) : val(x), next(next) {}
 };
 
class Solution 
{
public:
    int getLength(ListNode* head)
    {
        int n = 0;
        while (head)
        {
            n += 1;
            head = head->next;
        }
        return n;
    }
    ListNode* rotateRight(ListNode* head, int k) 
    {
        //由于k可能大于链表的长度,故对k预处理
        if (head == NULL) return head;
        int len = getLength(head);
        k %= len;
        if (k == 0)  return head;
        /*
        这道题的解题思路就是将倒数k个节点分割出来放到‘前面’去,那么如何确定分割点呢?
        设定两个指针p、q,q往后移k+1个位置(即p和q间隔k个位置),然后让q和p同时向后移动,当q=NULL时,
        p所在的位置就是分割点了。
        */
        ListNode* q = head, * p = head;
        for (int i = 0; i <= k; i++)
        {
            q = q->next;
        }
        while (q)
        {
            p = p->next;
            q = q->next;
        }

        //分割
        q = p->next;
        p->next = NULL;

        //分割完成,此时q是第二部分链表的头指针,接下来要让第二部分链表的尾节点接上原链表的头节点
        p = q;//这句代码执行后,p也变成了第二部分链表的头指针了

        //拼接,这个循环执行完之后,p就指向了第二部分链表的尾节点
        while (p->next)
        {
            p = p->next;
        }
        p->next = head;
        return q;
    }
};

删除链表倒数第N个节点(Leetcode-19):

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

struct ListNode
{
    int val;
    ListNode* next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode* next) : val(x), next(next) {}
};

class Solution
{
public:
    /*这道题与上道题的思路很想:要想删除第n个节点,就要找到第倒数第n+1个节点
    * 如何找呢?
    * 双指针+等距移动法
    */
    ListNode* removeNthFromEnd(ListNode* head, int n)
    {
        //如果只有5个节点,又恰好是删除倒数第5个位置的节点,那么N+1就不存在,为了避免这种情况,最好引入一个虚拟头节点
        ListNode new_head, * p = &new_head, * q = p;
        new_head.next = head;

        //让q指针往后移动n+1位
        for (int i = 0; i <= n; i++)
        {
            q = q->next;
        }

        //让p指向待删除节点的前一位
        while (q)
        {
            p = p->next;
            q = q->next;
        }
        
        //删除节点
        p->next = p->next->next;
        return new_head.next;
    }
};

环形链表Ⅱ(Leetcode-142):

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

/*环形链表Ⅱ的数学思路拆解:
*初始化一个前提:快指针每次前进2步,满指针每次前进1步。
*假设慢指针走了a的距离到达环的入口,
那么快指针就走了2a的距离,
设环的长度为x(也就是整个链表的长度减去a的长度),
那么快指针在环中经过的距离就是a,
于是快指针走完整个环的剩余长度就是x-a,
由于每一次‘行动’快指针都比慢指针多走1步,
那么两指针如果想在环中相遇,那么需要‘行动’x-a次,
此时二者相遇的位置沿环方向距离环入口的长度为a,
巧妙的是:链表的头节点距离环入口的距离也是a
*此时,如果把快慢指针中的一个扔回头节点,
再让它们以相同的速度向后移动,
那么它们再次相交的位置就是环入口。
*/
 struct ListNode 
 {
     int val;
     ListNode *next;
     ListNode(int x) : val(x), next(NULL) {}
 };

class Solution 
{
public:
    ListNode* detectCycle(ListNode* head) 
    {
        ListNode* p = head, * q = head;
        while (q && q->next)
        {
            p = p->next;
            q = q->next->next;
            if (p == q)
                break;
        }

        //循环结束有两种情况:①链表没有环②二者相遇了
        //排除第一种
        if (q == NULL || q->next == NULL)
        {
            return NULL;
        }

        p = head;//把p扔回头节点
        while (p != q)
        {
            p = p->next;
            q = q->next;
        }
        return q;
    }
};

反转链表Ⅱ(Leetcode-92):

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

/*
* 原链表由于左区间不为1,规模减小为子1;当左区间为1,右区间不为1,缩小规模为子2再到子3。
* 此时到达边界条件
*/
struct ListNode 
{
   int val;
   ListNode* next;
   ListNode() : val(0), next(nullptr) {}
   ListNode(int x) : val(x), next(nullptr) {}
   ListNode(int x, ListNode* next) : val(x), next(next) {}
   
};
class Solution 
{
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) 
    {
        //边界条件
        if (left == 1 && right == 1)
        {
            return head;
        }

        //左区间不为1
        if (left != 1)
        {
            head->next = reverseBetween(head->next, left - 1, right - 1);
        }
        //左区间为1,右区间不为1,此时也说明当前链表的头节点在反转区域内
        else
        {
            //记录反转完之后的尾节点
            ListNode* tail = head->next;

            //对剩下的部分进行反转
            ListNode* new_head = reverseBetween(head->next, left, right - 1);

            //将当前链表的头节点插入到记录的尾节点之后
            head->next = tail->next;
            tail->next = head;
            head = new_head;
        }
        return head;
    }
};

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值