Blog Feed

Học Tester Cho Người Trái Ngành

Công nghệ thông tin trong những năm gần đây liên tục phát triển. Thị trường nhân lực của ngành này cũng vì thế mà rầm rộ và nhộn nhịp hơn cả. Nhắc tới công nghệ thông tin là người ta nghĩ đầu tiên đến lập trình viên (Developer), quản trị, an ninh mạng, thiết kế phần mềm, quản lý hệ thống, kĩ sư cầu nối… Đó là những vị trí quen thuộc, phổ biến. Ở bài viết hôm nay, mình xin chia sẻ cụ thể Tester là gì, những điều cần thiết để trở thành 1 Tester, học tester cho người trái ngành.

I. Tester Là Làm Gì? Học Trái Ngành Có Làm Được Tester

Nhắc tới ngành CNTT thông thường các bạn chỉ nghĩ đến nhân viên lập trình, quản trị, bảo trì mạng chứ nhiều người không biết đến tester hay nhân viên kiểm thử. Tuy nhiên trong những năm gần đây thì tester lại là từ khóa được tìm kiếm phổ biến vậy tester là làm gì. Vì sao tester lại là một ngành hot như vậy trong những năm gần đây.

– Tester là làm gì

Tester là người kiểm thử phần mềm người thực hiện bới lông tìm vết để tìm kiếm các lỗi, sai sót, hay bất cứ vấn đề nào mà có thể tác động tới chất lượng phần mềm trước lúc bàn giao phần mềm đến tay khách hàng.

+ Các hướng đi của nghề kiểm thử:

Thông thường ngành tester sẽ có 2 con đường để bạn lựa chọn đó là Manual testing và Automation testing.

  • Manual testing: Đối với các bạn sinh viên mới ra trường hoặc mới chuyển sang ngành kiểm thử thủ công sẽ là sự lựa chọn đầu tiên, bạn sẽ tự động vào code khi làm, nhưng bạn phải nắm vững các khái niệm, kỹ thuật test manual và có tư duy tìm lỗi tốt.
  • Automation testing: Kiểm thử tự động là một kỹ thuật tự động hóa, trong đó người kiểm sử dụng các công cụ, script và phần mềm kiểm thử tự viết các tập code và sử dụng phần mềm phù hợp để kiểm thử phần mềm. Automation testing không cần nắm sâu kiến thức test manual nhưng thay vào đó họ phải biết rõ về các automation tools & frameworks cũng như có thể làm việc được trên nhiều ngôn ngữ lập trình khác nhau như Java, C#, AutoIT, Python, C++ v.v, tùy theo yêu cầu dự án.

+ Công việc của tester là gì?

  • Công việc của tester đó là tham gia quá trình kiểm thử các dự án phần mềm.
  • Tìm kiếm các lỗi của hệ thống phần mềm hoặc dựa vào những yêu cầu nghiệp vụ, kỹ thuật, mục đích sử dụng của phần mềm được đưa ra trong dự án nhân viên kiểm thử trực tiếp thẩm định, xác minh xem thử hệ thống phần mềm này có đáp ứng các yêu cầu kỹ thuật của dự án chưa.
  • Thiết kế và thực hiện test plan, test case, test report, tạo dữ liệu kiểm thử, thiết lập môi trường kiểm thử.
  • Thực hiện kiểm thử theo tính năng, hiệu năng của sản phẩm.
  • Tạo lỗi, kiểm soát lỗi, phối hợp với các developer để sửa lỗi, theo dõi kết quả kiểm thử để đảm bảo chất lượng dự án.
  • Tổng hợp các vấn đề phát sinh trong quá trình thực hiện dự án và đưa ra các đề xuất cải tiến.

+ Quy trình kiểm thử:

  • Nhận yêu cầu của khách hàng và phân tích yêu cầu
  • Lập kế hoạch kiểm thử, viết script cho trường hợp kiểm thử
  • Thực thi script
  • Debug lỗi
  • Báo cáo lỗi, Báo cáo kết quả kiểm thử
  • Báo cáo kết quả chạy Automation Test.
Tester Là Làm Gì? Học Trái Ngành Có Làm Được Tester
Tester Là Làm Gì? Học Trái Ngành Có Làm Được Tester

II. Trái Ngành Học Tester Có Khó Không

Trái nghành học tester có khó không là phân vân của không ít bạn khi muốn theo đuổi ngành này với hi vọng về một mức lương cao hơn so với mặt bằng chung các ngành khác mà không cần kiến thức quá chuyên sâu về lập trình. Vậy trái ngành học tester có thực sự dễ hoặc khó khăn như nhiều người vẫn nghĩ hay không?

+ Rất dễ để có thể theo đuổi nghề tester

Nếu bạn có một trình độ ngoại ngữ thành thạo, đặc biệt là kĩ năng đọc hiểu để có thể hiểu được yêu cầu và Test case thì bạn sẽ có thể trở thành một tester ( ví dụ như nếu bạn đang làm việc cho 1 công ty bắt buộc về trình độ tiếng anh thì trình độ đọc hiểu tiếng Anh của bạn phải tốt ).

Nội dung công việc của bạn sẽ bao gồm những gì? Công việc của bạn là đọc Test case và thực hành theo các đề nghị trong đó, công việc của bạn có vẻ sẽ không mấy thú vị. Nhiều bạn sau lúc hoàn thành khóa học Fresher Tester tại những trung tâm, rồi bắt đầu đi làm tại các công ty, họ than thở rằng: “Làm tester chán quá, với những dự án bảo trì hệ thống, ngày nào cũng làm đi làm lại chỉ có bấy nhiêu case. Khi thì test chỗ này, lúc lại test chỗ kia“.

+ Làm tester cũng không hề dễ

Khi mới bắt đầu đi làm, nhiều bạn nói rằng họ cảm thấy công việc quá khó khăn, họ không hiểu được nhóm dev nói gì, hay những nội dung về code mà các Dev nói ra họ không tài nào hiểu được. Với những trường hợp này thì công việc của tester không hề dễ chút nào.

Tuy nhiên bạn cũng không nên quá sợ hay e ngại mà bỏ qua cơ hội để học ngành Tester này, rất nhiều bạn sợ khó không biết có nên làm Tester hay không để rồi mãi vẫn chưa thể bắt đầu sẽ là rất đáng tiếc vì đây là một công việc rất tốt và nhiều cơ hội ở thời điểm hiện tại

+ Rốt cuộc làm nghề tester khó hay dễ?

Có thể nói làm nghề tester dễ là bởi vì bạn muốn test thì test, dù là test kĩ hay không, test nhiều hay ít thì bạn đều có thể thể làm được hết. Nếu đối với một người làm Dev, họ phải làm cho đến khi hoàn thành và ra mắt sản phẩm, chứ không thể nói là họ đã code xong trong khi chưa cho ra một sản phẩm hoàn chỉnh nào. Thì với mội Tester, bạn có thể nói bạn đã test xong.

Trong điều thứ 2 của nguyên tắc kiểm thử ISTQB nói rằng: Việc kiểm thử hết tất cả các kết hợp của giá trị đầu vào của một hệ thống là điều không thể. Vì vậy, khi test, bạn sẽ phải thực hiện dựa trên độ ưu tiên và rủi ro để có thể thực hiện test một cách hiệu quả.

Chính vì bạn không thể kiểm thử hết tất cả các trường hợp nên bạn phải biết mình cần test gì và không cần test gì. Để làm được như vậy thì bạn cần hiểu được đối tượng mà bạn đang test được làm ra như thế nào, hay nói cách khác bạn phải hiểu về sản phẩm. Chỉ khi bạn hiểu rõ về uqá trình vá cách làm ra sản phẩm thì bạn mới nhận ra được những rủi ro và thấy được khi nào thì sản phẩm đó không hoạt động được.

Và để làm được như thế, yếu cầu dành cho bạn là phải có một chút kiến thức về lập trình, về quá trình phát triển của phần mềm và nhiều kiến thức liên quan đến công nghệ thông tin khác.

Nói chung là, làm tester có khó không? việc dễ hay khó nó còn tuỳ thuộc vào thể loại sản phẩm, vào dự án, và kiến thức của người đang làm công việc test đó. Công việc tester thể dễ với người này, nhưng lại khó với người kia. Có thể nhàm chán với người này nhưng lại thú vị với người khác. Dù khó hay dễ thì Tester vẫn là một nghề rất HOT ở thời điểm hiện tại với mức thu nhập không hề thấp và được xem là nhẹ nhàng so với các công việc khác trong lĩnh vực công nghệ thông tin

Trái Ngành Học Tester Có Khó Không
Trái Ngành Học Tester Có Khó Không

III. Học Tester Cho Người Trái Ngành Bắt Đầu Từ Đâu

Tương tự với những người trái ngành học phần mềm tester thì các tester cần phải có là một nền tảng tốt về máy tính. Vậy học tester cho người trái ngành bắt đầu từ đâu? Đầu tiên là các bạn phải nắm được các kiến thức chung về phần mềm và máy tính. Ngoài ra bạn cũng cần bổ sung những kiến thức chuyên sâu để có thể đáp ứng được nhu cầu công việc khi đi làm thực tế.

+ Kiến thức chung

Về cơ bản, bạn cần phải nắm những kiến thức chung sau để có thể nghiên cứu chuyên sâu ngành nghề này:

  • Kiến thức căn bản về máy tính
  • Kiến thức tin học văn phòng căn bản
  • Biết cài đặt phần mềm, sử dụng internet thành thạo
  • Kiến thức về lập trình căn bản là SQL, HTML, CSS
  • Kỹ năng, kiến thức tổng quan về test: hiểu những định nghĩa, các thuật ngữ, quy trình phát triển phần mềm, quy trình test,…

+ Kiến thức chuyên sâu

Nếu bạn đi theo hướng Manual hoặc muốn phát triển hơn thì bắt buộc học thêm những kiến thức sau:

  • Create a Test Plan: phương pháp viết test plan
  • Design Test case: cách viết testcase thông dụng
  • Test Design Techniques: kỹ thuật thiết kế test case
  • Test reporting, Daily status report: cách viết báo cáo test case
  • Defect management: Finding defects, Logging defects, Tracking and managing defects – Report & quản lý bug, sử dụng tools tracking thông dụng như Jira, Mantis, Bugzilla, Application Lifecycle Management (ALM)
  • Mobile application testing: Cài đặt và test ứng dụng trên mobile
  • Windows, Website testing & Tools support: Test ứng dụng desktop, web, giả lập các trình duyệt khác nhau trên máy tính
  • Risk based testing process and implementation: Đánh giá rủi ro trong kiểm thử
  • Coding: SQL, HTML, CSS

Nếu bạn theo hướng Automation thì ngoài những kiến thức trên, bạn cần nắm thêm:

  • Lập trình: Java, C# (.Net) và ngôn ngữ khác sử dụng để hỗ trợ như AutoIT, Python
  • Automation Tool/Framework phổ biến như: Ranorex, Selenium, Appium, TestComplete
  • Các Tools khác như: Jmeter, SoapUI
Học Tester Cho Người Trái Ngành Bắt Đầu Từ Đâu
Học Tester Cho Người Trái Ngành Bắt Đầu Từ Đâu

IV. Học Tester Cho Người Trái Ngành Cần Những Gì

Dân trái ngành nên bắt đầu từ đâu

Đây là câu hỏi mà bất kỳ bạn nào trái ngành cũng hỏi và sẽ cảm thấy hoang mang không biết nên bắt đầu như thế nào? Cần học những gì? Bắt đầu học cái gì? Có nên đi học ở trung tâm không hay xin đi học việc, lộ trình thăng tiến như thế nào và đây chỉ là một số vấn đề mà hầu hết các bạn đều quan tâm.

Có thể do chưa có cái nhìn tổng quan về nghề nên bạn vẫn còn băn khoăn, đây cũng là vấn đề bình thường đối với những bạn trái ngành mà đôi lúc đúng chuyên ngành cũng gặp phải.

  1. Học 1 khoá cơ bản trước để biết được bạn có phù hợp với nghề này hay không.
  2. Tham gia chương trình đào tạo Tester căn bản chuyên sâu dành cho Tester. Chương trình đáng tham gia nhất dành cho mọi Tester, bạn sẽ sở hữu tất cả kỹ năng cần thiết của 1 Tester như: Phân tích dự án, Test Plan, Test Scenario, Test Case, Bug và cách dùng các công cụ hỗ trợ cho việc kiểm thử.
 Học Tester Cho Người Trái Ngành Cần Những Gì
Học Tester Cho Người Trái Ngành Cần  Học Tester Cho Người Trái Ngành Cần Những GìNhững Gì

V. Lộ Trình Học Tester Cho Người Trái Ngành

Lộ trình phát triển của nghề Tester cho người trái nghành cũng vô cùng rõ ràng và đầy tiềm năng, được chia theo các level sau:

Level 1: Fresher. Là những bạn mới tốt nghiệp các khóa đào tạo Tester cơ bản và bắt đầu đi làm Tester. Ở level này, những bạn Tester hoàn toàn là các bạn mới học xong các khóa học về Kiểm thử phần mềm, mới tiếp xúc môi trường doanh nghiệp, hoặc có thể là những người đã đi làm trái ngành mới thay đổi công việc sang Tester.

Level 2: Junior. Ở level junior, bạn Tester đã hiểu thực thi các test case, thêm vào đó, có thể báo cáo các bugs nếu có.

Level 3: Senior. Đây là những chuyên gia thành thạo về công nghệ testing, nắm rõ các yêu cầu kiểm thử phần mềm cho các doanh nghiệp với các ứng dụng phức tạp như tài chính, sức khỏe, thương mại điện tử…

Level 4: Test Leader. Thông thường, sau khoảng 5 năm kinh nghiệm trở lên, tester có thể nắm giữ vai trò quản lý. Những người này chịu trách nhiệm tổ chức công việc cần được thực hiện và phân công nhiệm vụ cụ thể cho các Tester trong team dự án. Tương ứng với số năm kinh nghiệm Test Leader có sẽ là quy mô lớn, nhỏ khác nhau mà các đội họ sẽ được quản lý.

Level 5: Test Manager. Là những người tổ chức và điều phối các nhóm kiểm thử (test team): quản lý metrics, lập kế hoạch chiến lược và đưa ra dự đoán.

Level 6: Senior Test Manager. Tùy thuộc vào độ cứng và số năm kinh nghiệm, Test Manager có thể đạt được vị trí Senior Test Manager.

Bên cạnh việc trở thành chuyên gia trong nghề Tester, Sau lúc có đủ kiến thức và kinh nghiệm ở level 4, bạn có thêm các hướng đi mới như: trở thành BA (Business Analyst) hoặc PM (Project Manager- quản lý dự án). Đây đều là các hướng phát triển rất tiềm năng cho các bạn Tester.

Lộ Trình Học Tester Cho Người Trái Ngành
Lộ Trình Học Tester Cho Người Trái Ngành

VI. Người Trái Ngành Nên Học Tester Ở Trung Tâm Nào

Tester hay còn gọi là kiểm thử phần mềm đang là 1 ngành nghề hot hiện nay trong lĩnh vực IT. Do nhu cầu nhân lực ngày càng tăng cao vậy nên các trung tâm đào tạo tester ngày càng phổ biến. Chính vì điều đó mà nhiều bạn trái nghành không biết mình nên học Tester ở đâu tốt nhất bởi có nhiều trung tâm đào tại Tester kém uy tín khiến bạn cảm thấy lo lắng khi đưa ra quyết định lựa chọn.

Và trong bài viết ngày hôm nay, Techacademy xin gửi đến bạn danh sách những địa chỉ đào tạo Tester hàng đầu Việt Nam được rất nhiều bạn học viên đánh giá cao để các bạn có thể tham khảo.

+ Trung Tâm Đào Tạo Techacademy

Techacademy là 1 trong các cơ sở đào tạo Tester có mặt tại địa bàn TP. Hà Nội. Các giảng viên của Techacademy đều là các Test Leader tới từ các công ty công nghệ như Viettel, FPT Software,… với nhiều năm kinh nghiệm trong nghề truyền tải cho học viên những kiến thức kiểm thử phần mềm chuyên nghiệp thực hành từ những dự án thật, các kỹ năng mềm cần thiết lúc làm việc tại các công ty phần mềm.

Áp dụng các cách dạy học tiên tiến, sáng tạo giúp buổi học trở nên sôi động với nhiều kiến thức bổ ích. Giúp học viên sau lúc học xong thi tuyển, phỏng vấn và làm việc tốt tại các tập đoàn và các công ty.

Vì sao bạn nên lựa chọn Trung tâm Techacademy?

  • Chương trình học phù hợp với tất cả đối tượng.
  • Hỗ trợ việc làm cho tất cả học viên.
  • Giáo trình bản quyền.
  • Giảng viên là Manager/ Leader Công ty nước ngoài và Tập Đoàn lớn trong nước có nhiều năm kinh nghiệm.
  • Học phí siêu siêu ưu đãi.
  • Hình thức học linh hoạt kết hợp online và offline (Phù hợp cả với các bạn tỉnh xa và nước ngoài)

Học các khóa học tại Techacademy, bạn sẽ được cung cấp đầy đủ các kiến thức căn bản từ cơ bản đến nâng cao để trở thành chuyên viên kiểm thử phần mềm chuyên nghiệp với phương pháp thực hành thực chiến trên dự án thật giúp học viên có cái nhìn về nghề tester, hiểu rõ nó không còn bỡ ngỡ thích nghi được với công việc khi làm việc tại các công ty phần mềm.

Với các khóa học tester cho người mới bắt đầu, bạn sẽ nắm chắc các kiến thức căn bản cùng như thành thạo các kỹ năng kiểm thử trong thực tế mà không chỉ có các khóa học offline tại trung tâm mà còn các khóa học tester Online đều được xây dựng lộ trình rõ ràng từ các chuyên gia.

Những năm qua, Techacademy vẫn luôn tự hào là cầu nối thành công giữa Nhà tuyển dụng và Học viên, giúp tạo cơ hội cho các bạn sinh viên ra trường, các nhân viên phần mềm nâng cao trình độ và cung cấp nguồn nhân lực chất lượng cho các Tập Đoàn và các Công Ty Phần Mềm.

Tham gia các khóa học tester tại Techacademy chính là nắm bắt cơ hội việc làm tốt, lương cao dành cho các bạn học CNTT, Hệ Thống Thông Tin, Toán Tin, An Toàn Thông Tin… và các bạn trái ngành muốn chuyển ngành. Hiện trung tâm có rất nhiều ưu đãi hấp dẫn mà bạn không thể bỏ lỡ. Vì thế hãy nhanh tay đăng ký ngay từ bây giờ bạn nhé!

+ Testing VN

Nếu bạn là dân IT về Tester chắc bạn không còn xa lạ gì với Testing VN (testing.vn), đây là một trong những trung tâm đi đầu trong lĩnh vực đào tester tại TP. HCM. Với nhiều năm kinh nghiệm trong việc đào tạo tester, khi đến với trung tâm chắc chắn bạn sẽ nhận lại được lượng kiến thức tận dụng trong khi làm việc.

Đến với khóa đào tạo tester tại Testing VN, có thể áp dụng được trong công việc, không giảng dạy truyền đạt những nội dung mang tính lý thuyết thiếu thực tế. Một khóa đào tạo tester sẽ giúp bạn nắm vững kiến thức hơn, đối với những người chưa biết gì thì có thể tìm hiểu rõ hơn về công việc này và hoàn toàn có thể tự tin ứng tuyển vào các công ty khi vừa hoàn thành khóa đào tạo.

Giới thiệu việc làm cho học viên, khi hoàn thành xong khóa đào tạo, học viên sẽ được trung tâm giới thiệu đến các tập đoàn các công ty liên kết với trung tâm, bạn sẽ không phải đau đầu về vấn đề tìm việc làm.

Tại sao nên học đào tạo tester tại Testing VN?

  • Đội ngũ nhân viên giảng dạy đẳng cấp nhiều năm kinh nghiệm có kiến thức chuyên sâu, kiến thức giảng dạy tốt giúp học viên tiếp thu một cách nhanh nhất.
  • Kiến thức không sáo rỗng, chú trọng vào thực tế không lan man những lý thuyết suông không thực tế.
  • Đội ngũ tư vấn nhiệt tình, sẵn sàng giải đáp mọi thắc mắc của học viên cho dù ngoài giờ học, chỉ cần bạn khúc mắc ở đâu cần giải thích chúng tôi sẽ hỗ trợ hết mình.
  • Phòng họp mới, hiện đại, rộng rãi, giúp học viên cảm thấy thỏa mái nhất khi đi học.
  • Học phí tham khảo: 3.250.000 đồng

Nếu bạn chưa biết lựa chọn đơn vị nào thì trung tâm đào tạo Tester Hà Nội sẽ là một lựa chọn đúng đắn và không khiến bạn thất vọng.

+ FPT Software Academy

Bạn đang muốn tham gia một khóa đào tạo bài bản, từ đầu về CNTT và nghề kiểm thử phần mềm – tester? Bạn muốn học kiểm thử phần mềm nhưng không thể tự học được ở nhà? Và đang băn khoăn với các khóa đào tạo Tester đắt đỏ ở Hà Nội? Bạn đang là sinh viên năm cuối ngành ngoại ngữ, kinh tế…. và vẫn loay hoay chưa định hướng được công việc tương lai?

Bạn muốn thử sức mình & theo đuổi lĩnh vực công nghệ thông tin – ngành đang có nhu cầu nhân lực nóng bỏng nhất trên thị trường lao động nhưng lo sợ kiến thức của mình không đủ? Bạn tự tin với khả năng ngoại ngữ của mình và sẵn sàng dấn thân vào thử thách tại lĩnh vực CNTT?

Nếu bạn đã quá mệt mỏi với những câu hỏi đó, thì FPT Software Academy là sự lựa chọn hoàn hảo dành cho bạn – nơi bạn có thể vừa được củng cố kiến thức, vừa được “thực chiến” lại vừa có “tiền”.

Tại đây, bạn sẽ được đào tạo từ đầu các kiến thức từ căn bản đến nâng cao về:

  • Tổng quan ngành phần mềm – CNTT, quy trình phát triển phần mềm
  • Các kiến thức, khái niệm cơ bản về kiểm thử phần mềm
  • Các giai đoạn kiểm thử trong dự án
  • Các loại & kỹ thuật kiểm thử căn bản.
  • Hệ quản trị cơ sở dữ liệu SQL dùng trong kiểm thử
  • Các kiến thức về: Concept Test, Test Process, Test level, Test Type, Test Plan…

Đặc biệt, bạn sẽ có cơ hội tham gia On-job-training tại các dự án phát triển phần mềm cho nhóm các thiết bị trên Ô tô hạng sang và trải nghiệm môi trường làm việc toàn cầu với các khách hàng lớn tại Đức, Nhật Bản và Hàn Quốc… Rèn luyện các kỹ năng mềm: Viết email, kỹ năng phỏng vấn, thuyết trình, kỹ năng phân tích giải quyết vấn đề. Nâng tầm khả năng ngoại ngữ: đọc dịch tài liệu chuyên ngành, giao tiếp thường xuyên tại Câu lạc bộ Tiếng Anh của Frehser Academy.

Học phí tham khảo: 8.900.000 đồng cho một khóa học Software Testing (2 tháng) tại FPT Software Academy, bạn sẽ nhận được: 03 khóa học chuyên sâu từ cơ bản đến nâng cao cùng hệ thống các bài thực hành Lab/mini Project để trở thành Chuyên viên Kiểm thử phần mềm chuyên nghiệp.

Người Trái Ngành Nên Học Tester Ở Trung Tâm Nào
Người Trái Ngành Nên Học Tester Ở Trung Tâm Nào

VII. Cách Viết CV Tester Cho Người Trái Ngành

Những ai là fan của IT chắc hẳn hiểu rất rõ về việc làm Tester và vai trò của nó trong đời sống hiện nay. Tuy nhiên, ngay cả những người dày mình kinh nghiệm cũng chưa chắc sở hữu việc làm bởi vì đây là công việc không chỉ đòi hỏi chuyên môn trình độ cao mà ứng viên cần phải chỉn chu khi tham gia ứng tuyển.

Chuẩn bị một mẫu CV Tester đầy đủ, chuẩn xác và hấp dẫn chắc chắn cơ hội của bạn sẽ được gia tăng hơn nhiều. Nếu chưa biết trình bày mẫu CV ngành nghề này vậy thì hãy tham khảo ngay bài viết sau đây do techacademy.edu.vn chia sẻ nhé.

Mỗi danh mục trong CV Tester đều có vai trò nhất định, ngoài ra nhà tuyển dụng thường dựa vào những thông tin chính yếu, lấy chúng làm căn cứ để đưa ra quyết định tuyển dụng cuối cùng. Bạn có biết những thông tin này là gì không?

+ Thông tin cá nhân trong CV Tester

Không có gì thay đổi và khác biệt so với những mẫu CV ngành nghề khác, ở CV Tester bạn vẫn cần cung cấp đủ thông tin cơ bản như Họ tên, năm sinh, giới tính, quê quán, địa chỉ liên hệ gồm có số điện thoại và email.

Thông tin cá nhân trong CV Tester
Thông tin cá nhân trong CV Tester

Lưu ý, số điện thoại và địa chỉ email tuyệt đối không viết sai, sau chọn lọc rất có thể bạn sẽ là ứng viên sáng giá bước vào vòng trong. Tuy nhiên nếu như bạn cho thông tin sai thì dù có chuyên nghiệp nữa thì nhà tuyển dụng cũng không thể liên lạc với bạn được, đành nhường cơ hội đấy cho người khác.

+ Học vấn trong CV Tester trình bày ra sao?

Học vấn hay còn gọi là Trình độ học vấn, ở mục này hãy ghi rõ hệ tốt nghiệp của bạn là Trung cấp, cao đẳng hay đại học; Chuyên ngành; Các khóa học ngắn hạn khác,…

Trong trường hợp bạn có thành tích xuất sắc, từng được khen thưởng hay sở hữu bằng tốt nghiệp loại giỏi thì đừng ngại ghi thông tin chi tiết vào để gia tăng cơ hội cho bản thân mình nhé.

Một Tester có thể tốt nghiệp ở 1 số trường uy tín như Học viện Công nghệ Bưu chính Viễn thông – Khoa Viễn thông, trường Đại học Công nghệ thông tin,…

+ Kỹ năng chuyên môn của Tester trong CV xin việc

Kỹ năng chuyên môn của Tester sẽ bao gồm tất cả những gì bạn sở hữu có thể phục vụ cho công việc của mình.

Kỹ năng chuyên môn của Tester trong CV xin việc
Kỹ năng chuyên môn của Tester trong CV xin việc

Một số kỹ năng mà bạn có thể kể đến như sau:

– Biết test API với Rest Client

– Làm tài liệu SRS, có khả năng tư duy và phân tích tốt, hỗ trợ tham gia phân tích nghiệp vụ

– Có khả năng viết Testcase tốt và dùng thành thạo

– Có thể test hiệu năng cơ bản với Tool JMeter

– …

Nói chung những kỹ năng nào mà bạn cảm thấy phù hợp và có liên quan trực tiếp tới công việc Tester thì có thể liệt kê vào mục này.

+ Kinh nghiệm nghề nghiệp trong CV Tester

Kinh nghiệm nghề nghiệp trong CV Tester có thể được viết theo nhiều trình tự khác nhau, nhưng chủ yếu vẫn ưu tiên cho công việc gần nhất sau đó mới tới những công việc xa hơn.

Tuy nhiên, khi liệt kê bạn cần ghi nhớ rằng những việc làm đó phải thực sự liên quan và phục vụ cho công việc Tester hiện tại. Đương nhiên để thuyết phục hơn thì bạn cần nêu ra thành quả mình đạt được trong quá trình làm việc đó, thành quả càng cao thì CV của bạn càng có giá trị.

Kinh nghiệm nghề nghiệp trong CV Tester
Kinh nghiệm nghề nghiệp trong CV Tester

Kinh nghiệm trong CV Tester bạn có thể trình bày theo ví dụ sau đây:

“10/2016 làm việc tại Trung tâm Đào tạo Tester XYZ, trong đó một số công việc đã làm cụ thể như sau:

– Tìm hiểu nghiệp vụ hệ thống sau đó viết testcase

– Thực hiện công việc test dự án, đôn đốc Dev fix khi có lỗi này xuất hiện

– Thực hiện làm test Report cho QTDA

– Có thể làm Performance testing với Tool JMeter

+ Trình bày Các dự án trong CV Tester

Đối với một Tester, thông tin dự án rất quan trọng, nó cho thấy năng lực thực sự của ứng viên khi đứng trước nhà tuyển dụng. Bạn nên ghi dự án như thế nào, hãy xem gợi ý dưới đây:

Trình bày Các dự án trong CV Tester
Trình bày Các dự án trong CV Tester

“Bạn tham gia dự án mang tên Hệ thống Quản lý Nhân sự với vai trò là Tester, trong đó:

– Thực hiện công tác tìm hiểu nghiệp vụ Đặc tả yêu cầu, biết lập test plan và viết testcase

– Thực hiện test dự án thường xuyên, đồng thời phối hợp với Developer để báo lỗi Bug hoặc defect

Nếu có thêm kinh nghiệm nào nữa thì bạn hãy viết theo gợi ý này nhé, hãy đưa ra khoảng 3 dự án để chứng minh bạn có thể làm tốt vai trò này.

The post Học Tester Cho Người Trái Ngành first appeared on Techacademy.

source https://techacademy.edu.vn/hoc-tester-cho-nguoi-trai-nganh/

Virtual Trong C++

Ở bài này, chúng ta sẽ tìm hiểu virtual trong C++. Từ khoá virtual có một số đặc tính khá thú vị mà mình muốn chia sẻ với các bạn qua bài viết ngày hôm nay. Hãy cùng Techacademy tìm hiểu nhé.

1. Virtual Trong C++ Là Gì ? Tác Dụng Của Virtual Trong C++

Một số tài liệu có viết công dụng của Virtual Function như sau:

“Virtual Function là để khai báo một function ở class cha (base class) mà sau đó các class kế thừa (derived class) có thể override function đó”

Nhưng chờ đã, có gì không ổn ở chỗ này, nếu chỉ là để override thôi thì mình hoàn toàn có thể khai báo function ở base class mà không cần virtual thì vẫn được cơ mà. Vậy ko lẽ đồng chí Virtual Function này vô dụng? Để làm rõ vấn đề cũng như hạn chế buồn ngủ vì phải đọc quá nhiều chữ, chúng ta thử xét ví dụ nhỏ sau:

class Buffalo {
public:
    void  action(){printf("I'm eating grass\n");};
}; 

class YoungBuffalo : public Buffalo {
    void action(){printf("I'm typing keyboard\n");};
};

int main()
{
  Buffalo *elon = new Buffalo();
  YoungBuffalo *andy = new YoungBuffalo();

  elon->action();
  andy->action();
}

Output sẽ ra như thế này:

I'm eating grass
I'm typing keyboard

Nếu chỉ xét đến đây thì cậu virtual chắc sẽ hơi buồn vì mọi chuyện có vẻ vẫn ổn mà không cần đến sự có mặt của nó. Vì vậy chúng ta thử xét tiếp 1 ví dụ khác để làm chỗ cho virtual toả sáng một chút.

class Buffalo {
public:
    void  action(){printf("I'm eating grass\n");};
}; 

class YoungBuffalo : public Buffalo {
public:
    void action(){printf("I'm typing keyboard\n");};
};

int main()
{
  Buffalo *elon = new Buffalo();
  Buffalo *andy = new YoungBuffalo(); // khác với lúc nãy là YoungBuffalo *andy = new YoungBuffalo();

  elon->action();
  andy->action();
}

Lần này output sẽ là như thế này:

I'm eating grass
I'm eating grass

Đến đây thì chắc không cần phải quá tinh mắt bạn cũng đã nhận ra vấn đề rồi đúng không. Mặc dù andy được tạo ra từ constructor của class YoungBuffalo thế nhưng nó hành xử lại như thể nó là một Buffalo. Thế nhưng ví dụ này trông hơi bị thiếu thông minh vì chả mấy ai khai báo Buffalo *andy = new YoungBuffalo(); như này để tự làm khó mình cả. Mình sẽ xét một ví dụ thực tế hơn chút nữa.

class Buffalo {
public:
    void  action(){printf("I'm eating grass\n");};
}; 

class YoungBuffalo : public Buffalo {
public:
    void action(){printf("I'm typing keyboard\n");};
};

void takeAnBuffalo(Buffalo* buffalo){
    buffalo->action();
}

int main()
{
  Buffalo *elon = new Buffalo();
  Buffalo *andy = new YoungBuffalo(); 
  takeAnBuffalo(elon);
  takeAnBuffalo(andy);
}

Output sẽ vẫn lại là:

I'm eating grass
I'm eating grass

Lúc này thì vấn đề thực sự đã rõ rồi, vì vậy chúng ta sẽ fix với vấn đề này với virtual như sau:

class Buffalo {
public:
    virtual void  action(){printf("I'm eating grass\n");}; // thêm virtual vào chỗ này
}; 

class YoungBuffalo : public Buffalo {
public:
    void action(){printf("I'm typing keyboard\n");};
};

void takeAnBuffalo(Buffalo* buffalo){
    buffalo->action();
}

int main()
{
  Buffalo *elon = new Buffalo();
  Buffalo *andy = new YoungBuffalo();
  takeAnBuffalo(elon);
  takeAnBuffalo(andy);
}

Output:

I'm eating grass
I'm typing keyboard
Virtual Trong C++ Là Gì ? Tác Dụng Của Virtual Trong C++
Virtual Trong C++ Là Gì ? Tác Dụng Của Virtual Trong C++

2. Pure Virtual Function Trong C++ Là Gì

Hàm thuần ảo (pure virtual function) được sử dụng khi:

  • Không cần sử dụng hàm này trong lớp cơ sở, chỉ phục vụ cho lớp dẫn xuất
  • Lớp dẫn xuất bắt buộc phải định nghĩa lại hàm thuần ảo

Ví dụ, chúng ta có 1 lớp cơ sở là Shape. Các lớp dẫn xuất của lớp Shape là Triangle, Square và Circle. Chúng ta muốn tính diện tích của các hình này.

Chúng ta có thể tạo ra một hàm thuần ảo có tên là calculateArea() trong lớp Shape. Các lớp Triangle, Square và Circle phải định nghĩa lại hàm calculateArea() với công thức tính diện tích khác nhau cho mỗi hình.

Hàm thuần ảo không có thân hàm và bắt buộc phải kết thúc với “= 0”.

class Shape{
public:
   // creating a pure virtual function
   virtual void calculateArea() = 0;
};

Lưu ý: Cú pháp “= 0” không phải là gán hàm thuần ảo có giá trị bằng 0. Nó chỉ là cú pháp cho biết đó là hàm thuần ảo (pure virtual function).

Pure Virtual Function Trong C++ Là Gì
Pure Virtual Function Trong C++ Là Gì

3. Virtual Destructor Trong C++

Trong một lớp thì Destructor có thể được đánh dấu làm hàm ảo còn Constructor thì không được đánh dấu là hàm ảo.

virtual Product();   // illegal
virtual ~Product();  // legal

Xét một vài ví dụ để làm rõ Virtual Destructor. Giả sử có lớp cha Base và một lớp con Derived được hiện thực như dưới đây.

Trường hợp 1: Phương thức lớp cha không đánh dấu Virtual

Phương thức hủy lớp cha không được đánh dấu là hàm ảo:

class Base
{
public:
   Base() {};
   ~Base() { cout << "Destructor Base\n"; };
};


class Derived : public Base
{
public:
   Derived() {};
   ~Derived() { cout << "Destructor Derived\n"; }
};

int main()
{
   Base *b = new Derived();
   delete b;

   return 0;
}

Sau khi Build và Debug, chỉ có dòng “Destructor Base” được xuất ra, có nghĩa là chỉ phương thức lớp cha được gọi nhưng phương thức của lớp con không được gọi. Dẫn đến có thể gây nên thiếu sót như không thu hồi bộ nhớ các cấp phát động của lớp cha hoặc các thủ tục cần thực hiện trước khi đối tượng được thu hồi.

Xét tiếp ví dụ dưới đây, tương tự như ví dụ trên nhưng có sửa đổi ở lớp Derived

class Base
{
public:
   Base() {};
   ~Base() { cout << "Destructor Base\n"; };
};


class Derived : public Base
{
private:
   int* m_array;
public:
   Derived() { this->m_array = new int[1024]; };
   ~Derived()
   {
      cout << "Destructor Derived\n";
      delete this->m_array;
   }
};

int main()
{
   Base *b = new Derived();
   delete b;
   return 0;
}

Với lớp con như trên, mặc dù định nghĩa phương thức để giải phóng m_array nhưng phương thức của lớp con không được gọi. Có nghĩa là chương tình đã rò rỉ 1024*4 bytes bộ nhớ.

Trường hợp 2: Phương thức lớp cha có đánh dấu Virtual

Phương thức lớp cha được đánh dấu là phương thức ảo:

class Base
{
public:
   Base() {};
   virtual ~Base() { cout << "Destructor Base\n"; };
};


class Derived : public Base
{
public:
   Derived() {};
   ~Derived() { cout << "Destructor Derived\n"; }
};

int main()
{
   Base *b = new Derived();
   delete b;
   return 0;
}

Bulid và Debug thì chương trình xuất ra hai dòng là

Destructor Derived
Destructor Base

Như vậy phương thức hủy của lớp con được gọi trước sau đó mới gọi phương thức hủy lớp cha và các thủ tục cần thiết trước khi hủy các đối tượng đã được thực hiện đầy đủ.

Virtual Destructor Trong C++
Virtual Destructor Trong C++

4. Nhược Điểm Của Các Hàm Ảo

Vì hầu hết thời gian bạn sẽ muốn các hàm của mình là ảo, tại sao không làm cho tất cả các hàm trở nên ảo? Câu trả lời là bởi vì nó không hiệu quả – việc giải quyết một cuộc gọi hàm ảo mất nhiều thời gian hơn là giải quyết một cuộc gọi thông thường. Hơn nữa, trình biên dịch cũng phải cấp phát một con trỏ phụ cho mỗi đối tượng lớp có một hoặc nhiều hàm ảo.

Nhược Điểm Của Các Hàm Ảo
Nhược Điểm Của Các Hàm Ảo

5. Tại Sao Không Nên Gọi Các Hàm Ảo Từ Các Hàm Tạo Hoặc Hàm Hủy

Ở đây, Bạn không nên gọi các hàm ảo từ các hàm tạo hoặc hàm hủy. Tại sao?

Hãy nhớ rằng khi một lớp Derived được tạo, phần Base được xây dựng trước. Nếu bạn đã gọi một hàm ảo từ hàm tạo cơ sở và phần lớp Derived thậm chí chưa được tạo, thì nó không thể gọi hàm của Derived vì không có đối tượng Derived được khởi tạo để gọi hàm. Trong C ++, nó sẽ gọi hàm trong class Base thay thế.

Một vấn đề tương tự tồn tại cho hàm huỷ. Nếu bạn gọi một hàm ảo trong hàm hủy của lớp Cơ sở, nó sẽ luôn gọi hàm của lớp Cơ sở, bởi vì phần Derived của lớp đã bị hủy.

Quy tắc: Không bao giờ gọi các hàm ảo từ các hàm tạo hoặc hàm hủy.

Tại Sao Không Nên Gọi Các Hàm Ảo Từ Các Hàm Tạo Hoặc Hàm Hủy
Tại Sao Không Nên Gọi Các Hàm Ảo Từ Các Hàm Tạo Hoặc Hàm Hủy

6. Tại Sao Bạn Nên Khai Báo Một Hàm Hủy Là Ảo

Giả sử có class Parent và class Child kế thừa từ Parent.

Ta định nghĩa con trỏ: Parent * p = new Child();

Lúc này để tạo ra Child(), thì Parent() phải được tạo ra trước. Khi chúng ta delete p, thì cả 2 đối tượng này cũng phải được gọi Destructor. Vậy nếu không khai báo virtual cho hàm Destructor của Parent, thì chỉ Destructor của Parent được gọi, đối tượng Child() vẫn còn đó.

Tại Sao Bạn Nên Khai Báo Một Hàm Hủy Là Ảo
Tại Sao Bạn Nên Khai Báo Một Hàm Hủy Là Ảo

The post Virtual Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/virtual-trong-c/

Các Thuật Toán Tìm Kiếm Trong C++

Trong hầu hết các hệ quản lý dữ liệu, thao tác tìm kiếm thường được thực hiện nhất để khai thác thông tin khác nhau. Bởi vậy, khi xây dựng một hệ quản lý thông tin trên máy tính, bên cạnh các thuật toán tìm kiếm, các thuật toán sắp xếp dữ liệu cũng là một trong những chủ đề được quan tâm hàng đầu. Hãy cũng mình tìm hiểu về thuật toán tìm kiếm phổ biến nhé.

1. Thuật Toán Tìm Kiếm Là Gì

Tìm kiếm là quá trình tìm một phần tử nằm trong một tập hợp nhiều phần tử dựa vào một miêu tả nào đó. Ví dụ bạn cần tìm đồng 10k trong một đống tiền từ 10k đến 100k thì quá trình đó ta gọi là tìm kiếm.

Nếu một tập hợp đã được sắp xếp thì quá trình tìm kiếm trong tập hợp đó sẽ nhanh hơn. Ở thí dụ trên nếu đống tiền từ 10k đến 100k đã được sắp xếp tăng dần hoặc giảm dần thì ta cực kỳ dễ dàng tìm kiếm tờ 10k. Nhưng nếu đó là 1 đống tiền lộn xộn thì bạn sẽ mất nhiều thời gian để xử lý chúng.

Vậy thuật toán tìm kiếm là thuật toán dùng để kiếm tìm phần tử trong 1 danh sách cho trước theo một tiêu chí nhất định. Chúng ta thường sử dụng hai thuật toán đó là thuật toán tìm kiếm tuyến tính và thuật toán tìm kiếm nhị phần.

Thuật toán tìm kiếm tuyến tính là quá trình tìm kiếm trong một tập hợp chưa sắp xếp, giống với đống tiền chưa được sắp xếp ở ví dụ trên. Còn thuật toán tìm kiếm nhị phân là quá trình tìm kiếm trong một dãy đã được sắp xếp. Cả hai thuật toán đều có những ưu và nhược điểm khác nhau.

2. Các Thuật Toán Tìm Kiếm Trong C++

Trong bài viết này, mình sẽ giới thiệu đến các bạn các thuật toán tìm kiếm trong C ++ phổ biến nhất. Giờ hãy bắt đầu tìm hiểu về các thuật toán tìm kiếm này nhé!

+ Thuật Toán Tìm Kiếm Tuyến Tính

Thuật toán tìm kiếm tuyến tính (linear search) hay còn gọi là thuật toán tìm kiếm tuần tự (Sequential search) là một phương pháp tìm kiếm một phần tử cho trước trong một danh sách bằng cách duyệt lần lượt từng phần tử của danh sách đó cho đến lúc tìm thấy giá trị mong muốn hay đã duyệt qua toàn bộ danh sách.

Tìm kiếm tuyến tính là một giải thuật rất đơn giản khi hiện thực. Giải thuật này tỏ ra khá hiệu quả khi cần tìm kiếm trên một danh sách đủ nhỏ hoặc một danh sách chưa sắp thứ tự đơn giản. Trong trường hợp cần tìm kiếm nhiều lần, dữ liệu thường được xử lý một lần trước khi tìm kiếm: có thể được sắp xếp theo thứ tự, hoặc được xây dựng theo một cấu trúc dữ liệu đặc trưng cho giải thuật hiệu quả hơn,…

Bài toán: Cho một mảng mảng [] gồm n phần tử, hãy viết hàm để tìm kiếm một phần tử x đã cho trong mảng [].

Thuật Toán Tìm Kiếm Tuyến Tính
Thuật Toán Tìm Kiếm Tuyến Tính

Ví dụ:

Đầu vào: mảng A[] = {10, 20, 80, 30, 60, 50, 
                     110, 100, 130, 170}
          x = 110;
Đầu ra: 6
Phần tử x có mặt ở vị trí số 6

Đầu vào: mảng A[] = {10, 20, 80, 30, 60, 50, 
                     110, 100, 130, 170}
           x = 175;
Đầu ra: -1
Phần tử x không có trong mảng A[].

Mã giả

Phiên bản lặp tự nhiên

Đây là phiên bản hay gặp nhất của giải thuật này, kết quả trả về sẽ là vị trí của phần tử cần tìm hoặc một giá trị Δ thể hiện việc không tìm thấy phần tử trong danh sách đó.

1. For each item in the list:
    1. if that item has the desired value,
        1. stop the search and return the item's location.
2. Return 'Δ'

Nếu danh sách được lưu trữ dưới dạng mảng, vị trí của phần tử cần tìm có thể là chỉ số của nó trong mảng, còn giá trị Δ có thể là chỉ số nằm trước phần tử đầu tien (0 hoặc -1 tùy vào danh sách).

Nếu danh sách là một danh sách liên kết, vị trí của phần tử được trả về có thể nằm dưới dạng địa chỉ của no, còn giá trị Δ có thể là giá trị null.

Phiên bản đệ quy

Đây là phiên bản đệ quy khi hiện thực giải thuật tìm kiếm tuần tự.

1. if the list is empty, return Λ;
2. else
   1. if the first item of the list has the desired value
      1. return its location;
   2. else 
      1. search the value in the remainder of the list, and return the result.

Sử dụng phần tử cầm canh

Một phương pháp được sử dụng để cải thiện hiệu quả của giải thuật là chèn phần tử muốn tìm kiếm và cuối danh sách như một phần tử cầm canh (sentinel) như được trình bày dưới đây:\

1. Set A[n + 1] to x. 
2. Set i to 1.
3. Repeat this loop:
    1. If A[i] = x, 
       1. exit the loop.
    2. Set i to i + 1.
4. Return i.

Việc thêm phần tử cầm canh giúp giảm bớt việc so sánh chỉ số hiện tại i với số các phần tử n ở mỗi vòng lặp. Tuy nhiên, điều này chỉ có thể được sử dụng khi vị trí cuối cùng của danh sách tồn tại nhưng chưa được sử dụng.

Viết thuật toán tìm kiếm tuyến tính với ngôn ngữ lập trình C, C++, Java, Python3

Tìm kiếm tuyến tính với C++:

#include <iostream> 
using namespace std; 
  
int search(int arr[], int n, int x) 
{ 
    int i; 
    for (i = 0; i < n; i++) 
        if (arr[i] == x) 
            return i; 
    return -1; 
} 
  
int main(void) 
{ 
    int arr[] = { 2, 3, 4, 10, 40 }; 
    int x = 10; 
    int n = sizeof(arr) / sizeof(arr[0]); 
    int result = search(arr, n, x); 
   (result == -1)? cout<<"Element is not present in array" 
                 : cout<<"Element is present at index " <<result; 
   return 0; 
}

Tìm kiếm tuyến tính với C:

#include <stdio.h> 
  
int search(int arr[], int n, int x) 
{ 
    int i; 
    for (i = 0; i < n; i++) 
        if (arr[i] == x) 
            return i; 
    return -1; 
} 
  
int main(void) 
{ 
    int arr[] = { 2, 3, 4, 10, 40 }; 
    int x = 10; 
    int n = sizeof(arr) / sizeof(arr[0]); 
    int result = search(arr, n, x); 
    (result == -1) ? printf("Element is not present in array") 
                   : printf("Element is present at index %d", 
                            result); 
    return 0; 
}

Tìm kiếm tuyến tính với Python3:

def search(arr, n, x): 
  
    for i in range (0, n): 
        if (arr[i] == x): 
            return i; 
    return -1; 
  
# Driver Code 
arr = [ 2, 3, 4, 10, 40 ]; 
x = 10; 
n = len(arr); 
result = search(arr, n, x) 
if(result == -1): 
    print("Element is not present in array") 
else: 
    print("Element is present at index", result);

Tìm kiếm tuyến tính với Java:

class GFG  
{  
public static int search(int arr[], int x) 
{ 
    int n = arr.length; 
    for(int i = 0; i < n; i++) 
    { 
        if(arr[i] == x) 
            return i; 
    } 
    return -1; 
} 
  
public static void main(String args[]) 
{ 
    int arr[] = { 2, 3, 4, 10, 40 };  
    int x = 10; 
      
    int result = search(arr, x); 
    if(result == -1) 
        System.out.print("Element is not present in array"); 
    else
        System.out.print("Element is present at index " + result); 
} 
}

Tìm kiếm tuyến tính với PHP:

<?php 
  
function search($arr, $x) 
{ 
    $n = sizeof($arr); 
    for($i = 0; $i < $n; $i++) 
    { 
        if($arr[$i] == $x) 
            return $i; 
    } 
    return -1; 
} 
  
// Driver Code 
$arr = array(2, 3, 4, 10, 40);  
$x = 10; 
      
$result = search($arr, $x); 
if($result == -1) 
    echo "Element is not present in array"; 
else
    echo "Element is present at index " , 
                                 $result; 
?>

Tìm kiếm tuyến tính với C#:

using System;  
  
class GFG  
{  
    public static int search(int[] arr, int x) 
    { 
        int n = arr.Length; 
        for(int i = 0; i < n; i++) 
        { 
            if(arr[i] == x) 
                return i; 
        } 
        return -1; 
    } 
      
    public static void Main() 
    { 
        int[] arr = { 2, 3, 4, 10, 40 };  
        int x = 10; 
          
        int result = search(arr, x); 
        if(result == -1) 
            Console.WriteLine("Element is not present in array"); 
        else
            Console.WriteLine("Element is present at index "+ result); 
    } 
}

Chạy thử và xem kết quả:

Element is present at index 3

+ Thuật Toán Tìm Kiếm Nhị Phân

Thuật toán tìm kiếm nhị phân là một trong các thuật toán sắp xếp được sử dụng rất nhiều trong thực tế. Hãy cùng mình đi tìm hiểu thuật toán tìm kiếm này nhé.

Tìm kiếm là một phần không thể thiếu của mọi ứng dụng, website hay phần mềm. Tính năng tìm kiếm cho phép người sử dụng nhanh chóng truy vấn và tìm kiếm các bản ghi theo mong muốn. Và một công cụ tìm kiếm nổi tiếng nhất hàng ngày chúng ta vẫn thường sử dụng đó là Google search.

Bài viết ngày hôm nay Techacademy sẽ giới thiệu cho độc giả một thuật toán tìm kiếm tối ưu áp dụng đối với trường hợp dữ liệu số đã sắp xếp.

Phát biểu thuật toán tìm kiếm nhị phân(Binary search)

Cho một mảng đã sắp xếp arr[] có n phần tử, viết một hàm tìm kiếm trả về chỉ số của phần tử có giá trị x trong arr[].

Giải thuật đơn giản nhất cho bài toán này là sử dụng linear search(tìm kiếm tuyến tính). Tức là bạn sẽ phải đi qua từng phần tử của mảng để đối chiếu với x cần tìm. Thuật toán này trong trường hợp xấu nhất cho độ phức tạp là O(n). Mình cũng sẽ minh họa code của thuật toán này dưới đây.

Đây là code C/C++ sử dụng linear search.

 
// Code from https://techacademy.edu.vn
 
#include <stdio.h>
 
int search(int arr[], int n, int x)
{
  int i;
  for (i = 0; i < n; i++)
    if (arr[i] == x)
      // Trả về chỉ số khi tìm thấy
      return i;
  // Nếu không tìm thấy trả về -1. Vì chỉ số mảng >= 0
  return -1;
}
 
int main() {
  int arr[] = {2, 3, 4, 10, 40};
  int n = sizeof(arr) / sizeof(arr[0]);
  int x = 10;
  int result = search(arr, n, x);
  if (result != -1) {
    printf("%d xuat hien tai chi so %d", x, result);
  } else {
    printf("%d khong co trong mang", x);
  }
  return 0;
}

Ý tưởng của thuật toán tìm kiếm nhị phân

Chú ý: Trong bài viết tôi giả sử mảng đang sắp xếp tăng dần. Với trường hợp mảng sắp xếp giảm dần, bạn đọc sau khi hiểu thuật toán sẽ có thể tự làm.

Do tính chất mảng đã sắp xếp, công việc tìm kiếm phần tử x có thể triển khai như sau:

  1. Xét đoạn mảng arr[left…right] cần tìm kiếm phần tử x. Ta so sánh x với phần tử ở vị trí giữa của mảng(mid = (left + right)/2). Nếu:
  2. Nếu phần tử arr[mid] = x. Kết luận và thoát chương trình.
  3. Nếu arr[mid] < x. Chỉ thực hiện tìm kiếm trên đoạn arr[mid+1…right].
  4. Nếu arr[mid] > x. Chỉ thực hiện tìm kiếm trên đoạn arr[left…mid-1].

Bằng cách áp dụng thuật toán tìm kiếm nhị phân, độ phức tạp cho trường hợp xấu nhất là O(log(n)).

Minh họa code cho thuật toán tìm kiếm nhị phân

Trong phần này, mình sẽ minh họa code sử dụng giải thuật đệ quy dùng Java và C/C++. Ngoài ra, tôi sẽ áp dụng thêm giải thuật khử đệ quy với C/C++.

Code minh họa C/C++ sử dụng đệ quy

 
// Code from https://nguyenvanhieu.vn
 
#include <stdio.h>
 
// Hàm tìm kiếm nhị phân sử dụng giải thuật đệ quy
int binarySearch(int arr[], int l, int r, int x) {
  if (r >= l) {
    int mid = l + (r - l) / 2; // Tương đương (l+r)/2 nhưng ưu điểm tránh tràn số khi l,r lớn
 
    // Nếu arr[mid] = x, trả về chỉ số và kết thúc.
    if (arr[mid] == x)
      return mid;
 
    // Nếu arr[mid] > x, thực hiện tìm kiếm nửa trái của mảng
    if (arr[mid] > x)
      return binarySearch(arr, l, mid - 1, x);
 
    // Nếu arr[mid] < x, thực hiện tìm kiếm nửa phải của mảng
    return binarySearch(arr, mid + 1, r, x);
  }
 
  // Nếu không tìm thấy
  return -1;
}
 
int main(void) {
  int arr[] = {2, 3, 4, 10, 40};
  int n = sizeof(arr) / sizeof(arr[0]);
  int x = 10;
  int result = binarySearch(arr, 0, n - 1, x);
  if (result == -1)
    printf("%d xuat hien tai chi so %d", x, result);
  else
    printf("%d xuat hien tai chi so %d", x, result);
  return 0;
}

Code minh họa sử dụng đệ quy Java

 
// Code from https://nguyenvanhieu.vn
 
class BinarySearch {
  
  int binarySearch(int arr[], int l, int r, int x) {
    if (r >= l) {
      int mid = l + (r - l) / 2;
 
      // Nếu arr[mid] = x, trả về chỉ số và kết thúc
      if (arr[mid] == x)
        return mid;
 
      // Nếu arr[mid] > x, gọi đệ quy tìm kiếm bên trái
      if (arr[mid] > x)
        return binarySearch(arr, l, mid - 1, x);
 
      // Ngược lại, nếu arr[mid] < x, gọi đệ quy tìm kiếm bên phải
      return binarySearch(arr, mid + 1, r, x);
    }
 
    // Trong trường hợp không tìm thấy
    return -1;
  }
 
 
  public static void main(String args[]) {
    BinarySearch ob = new BinarySearch();
    int arr[] = {2, 3, 4, 10, 40};
    int n = arr.length;
    int x = 10;
    int result = ob.binarySearch(arr, 0, n - 1, x);
    if (result == -1)
      System.out.println("Không tìm thấy phần tử " + x);
    else
      System.out.println("Phần tử " + x + " được tìm thấy tại chỉ số " +
                         result);
  }
}

Code khử đệ quy sử dụng C/C++

 
// Code from https://nguyenvanhieu.vn
 
#include <stdio.h>
 
// Hàm tìm kiếm nhị phân sử dụng giải thuật đệ quy
int binarySearch(int arr[], int n, int x) {
  int r = n - 1; // chỉ số phần tử cuối
  int l = 0; // Chỉ số phần tử đầu tiên
  while (r >= l) {
    int mid = l + (r - l) / 2; // Tương đương (l+r)/2 nhưng ưu điểm tránh tràn số khi l,r lớn
 
    // Nếu arr[mid] = x, trả về chỉ số và kết thúc.
    if (arr[mid] == x)
      return mid;
 
    // Nếu arr[mid] > x, cập nhật lại right
    if (arr[mid] > x)
      r = mid - 1;
    // Nếu arr[mid] < x, cập nhật lại left
    if (arr[mid] < x)
      l = mid + 1;
  }
 
  // Nếu không tìm thấy
  return -1;
}
 
int main(void) {
  int arr[] = {
    2,
    3,
    4,
    10,
    40
  };
  int n = sizeof(arr) / sizeof(arr[0]);
  int x = 10;
  int result = binarySearch(arr, n, x);
  if (result == -1)
    printf("%d xuat hien tai chi so %d", x, result);
  else
    printf("%d xuat hien tai chi so %d", x, result);
  return 0;
}

+ Thuật Toán Tìm Kiếm Nội Suy

Thuật toán tìm kiếm nội suy là một sự cải tiến của tìm kiếm nhị phân Binary Search. Nó có xu hướng tiến gần đến vị trí, giá trị tìm kiếm. Do đó tốc độ tìm kiếm được tối ưu rất cao và nhanh hơn nhiều so với Binary Search.

Cách thức hoạt động của nó dựa trên Binary Search, nhưng có sự cải tiến hơn. Đó chính là nó tìm ra phần tử gần với giá trị tìm kiếm nhất và bắt đầu từ đó để tìm.

Ví dụ:

Chúng ta có danh sách các sinh viên trong một lớp. Nếu chúng ta muốn tìm một bạn tên Tiến, thì chúng ta sẽ ưu tiên việc tìm kiếm từ cuối danh sách. Chứ không nên tìm kiếm từ đầu danh sách vì điều đó rất mất thời gian.

Với Interpolation Search nó sẽ linh hoạt hơn rất nhiều trong lúc tìm kiếm

Giả xử chúng ta có:

  • Left, right là hai vị trí đầu và cuối.
  • T là tập.
  • X là giá trị cần tìm.

Giải thích thuật toán

Bước 1:Chúng ta sẽ sử dụng công thức tìm phần tử chính giữa của tập theo cách tìm kiếm Binary Search:

Search = left + (right - left) * 1/2

Trong công thức trên chúng ta sẽ thay giá trị 1/2 bằng biểu thức sau:

(X - T[left]) / (T[right] - T[left])

Sau khi thay biểu thức vào công thức sẽ được công thức mới như sau:

Search = left + (X- T[left]) * (right – left) / (T[right] – T[left])

Bước 2: Bây giờ chúng ta sẽ kiểm tra T[Search] nếu bằng X thì Search chính là vị trí cần tìm.

  • Nếu Search < X thì tăng left lên một đơn vị rồi quay lại bước 1.
  • Nếu Search > X thì giảm right xuống một đơn vị rồi quay lại bước 1.

Thuật toán tìm kiếm Interpolation Search

int InterPolationSearch(int arr[], int n, int x)
{
  int left = 0;
  int right = n-1;
  while (left <= right && x >= arr[left] && x <= arr[right])
  {
    double val1 = (double) (x - arr[left]) / (arr[right]-arr[left]);
    int val2 = (right-left);
    int Search = left + val1*val2;
  
    if (arr[Search] == x)
      return Search;
  
    if (arr[Search] < x)
      left = Search + 1;
    else
      right = Search - 1;
  }
  return -1;
}

+ Thuật Toán Ternary Search

Tương tự với thuật toán tìm kiếm nhị phân, Ternary Search – Tìm kiếm tam phân là một kỹ thuật trong khoa học máy tính dùng để tìm kiếm giá trị lớn nhất (maximum) hay nhỏ nhất (minimum) của một unimodal function, và đây cũng là một ví dụ ứng dụng lớp thuật toán Chia để trị (divide and conquer).

Thuật Toán Ternary Search
Thuật Toán Ternary Search

Độ phức tạp thời gian: O (log [n]) trong đó cơ số của log = 3

Độ phức tạp của không gian: O (1) để thực hiện lặp lại trong khi O (log [n]) để thực hiện đệ quy

Ví dụ:

#include<iostream>
using namespace std;

int ternaryI(int a[], int target, int n) 
{
    int l = 0;
    int r = n-1;

    while( r-l>=0 ) {
        int partiton = (r-l)/3;
        int mid1 = l + partiton;
        int mid2 = r - partiton;

        if ( target == a[mid1])
            return mid1;
        else if ( target == a[mid2]) 
            return mid2;
        else if ( target < a[mid1] ) 
            r = mid1-1;
        else if ( target > a[mid2] )
            l = mid2+1;
        else {
            l = mid1+1;
            r = mid2-1;
        } 
    } // while ends

    return -1;
} // function ends

int main() 
{
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int n = 10;
    int target = 7;

    int tsi = ternaryI(arr, target, n);
    cout << "Result of TSI = " << tsi << endl;

    return 0;
} // main ends

+ Thuật Toán Jump Search

Bài hôm nay chúng ta cùng tìm hiểu về thuật toán Jump Search (Thuật toán tìm kiếm nhảy)

Nguyên lý cơ bản của Jump Search

Cũng giống như giải thuật Binary Search. Jump Search cũng yêu cầu danh sách đã cho phải là một danh sách đã được sắp xếp theo thứ tự đã biết.

Ví dụ là tăng dần.

Cơ chế của Jump Search đó là tìm ra một hệ số nhảy được tính bằng : Căn bậc hai của số phần tử.

Từ hệ số tìm được, Jump Search sẽ thực hiện nhảy phần tử theo hệ số để tìm ra phần từ lớn hơn giá trị tìm kiếm.

=> Phần tử tìm kiếm sẽ nằm trong khoảng của nhảy mà chứa phần từ lớn hơn giá trị tìm kiếm ở trên.

Minh Họa hình vẽ như sau:

Thuật toán Jump Search
Thuật toán Jump Search

Tôi có tập 9 phần từ đã được sắp xếp theo thứ tự tăng dần.

Tôi xác định giá trị nhảy là step = √9 = 3 .

Giả sử giá trị cần tìm là 33.

Sử dụng một biến prev để lưu vị trí bắt đầu.

Một biến jump để nhảy.

Step 1:

Thuật toán Jump Search
Thuật toán Jump Search

Gán prev = jump = 6

Nhảy jump = jump + step = 9 (bằng số phần tử, nếu jum lớn hơn n thì gán jum = n)

Lại kiểm tra T[jump – 1] => T[8] = 44 > 33 => Đã tìm được khoảng chứa giá trị cần tìm.

Khoảng chứa giá trị cần tìm là từ prev = 6 , jump = 9.

Lúc này chúng ta chỉ việc kiểm tra tuyến tính tệp từ vị trí prev đến jump để tìm ra giá trị cần tìm.

Sử dụng for hoặc while để duyệt phần tử trong khoảng prev – jump.

Các tính chất thuật toán.

– Tập cần tìm kiếm là phải được xắp xếp trước và biết rõ theo chiều tăng giảm.

– ĐỘ PHỨC TẠP HAY THỜI GIAN THỰC THI CỦA THUẬT TOÁN LÀ: O = √(n)

Thực hành Jump Search

Bây giờ từ nguyên lý ở trên chúng ta sẽ cùng thực hành với code c++

Hàm JumpSearch

 
 
// Techacademy.edu.vn
#include <iostream>
#include <conio.h>
#include <math.h>
 
int JumpSearch(int arr[], const int& n, const int& x)
{
  int step = sqrt(1.0*n);
  int prev = 0;
 
  int jump = step;
  //Find element > x
  while(arr[jump - 1] < x)
  {
    prev = jump;
    jump +=  step;
    if (jump > n)
      jump = n;
    if (prev > n)
      return -1;
  }
 
  // When found element > x.
  while(arr[prev] < x)
  {
    prev ++;
    if (prev == jump)
    {
      return -1;
    }
  }
 
  if (arr[prev] == x)
    return prev;
  return -1;
}
 

Hàm Main

 
 
int main(int argc, _TCHAR* argv[])
{
  int arr[] = {0, 1, 4, 7, 8, 9, 14, 16, 18, 19, 30, 34, 46, 48, 50, 55, 58};
  int s_val = 55;
  unsigned int num = sizeof(arr)/sizeof(arr[0]);
  int ret1 = JumpSearch(arr, num, s_val);
 
  if (ret1 != -1)
    std::cout << "Vi tri cua " << s_val <<  "la : " << ret1;
  else 
    std::cout << "Khong tim dc";
 
  _getch();
  return 0;
}
 

+ Thuật Toán Exponential Search

Bài hôm nay chúng ta cùng tìm hiểu về thuật toán Exponential Search (Thuật toán tìm kiếm theo số mũ)

Nguyên lý Exponential Search

+ Cũng yêu cầu dãy số đầu vào là một dãy đã được xắp xếp cho sẵn.

+ Kết hợp với việc tìm kiếm theo kiểu nhị phân.

+ Tìm ra khoảng con chứa giá trị cần tìm kiếm, và sau đó sử dụng tìm kiếm nhị phân cho khoảng con đó.

Độ phức tạp thời gian mà nó đạt được là: Log2(n).

Step 1:

Thuật Toán Exponential Search
Thuật Toán Exponential Search

+ Kiểm tra phần tử 0 của tập để xem nó có phải là phần tử tìm kiếm hay không.

+ Nếu không thì bắt đầu giá trị chạy từ 1 và kiểm tra nó < giá trị cần tìm hay không.

=> Nếu có, thì tiếp tục tăng giá trị chạy lên theo công thức : i = i*2;

=> Nếu không, thì chứng tỏ giá trị cần tìm đang năm trong khoảng từ i/2 đến i

Vì a[i] > X và vòng lặp trước đó là a[i/2] < X.

Do đó ta tìm được khoảng con chứa giá trị X cần tìm

Step 2:

Áp dụng Binary Search cho khoảng con vừa tìm được.

Thực hành

 
 
// Techacademy.edu.vn
#include <iostream>
#include <conio.h>
 
int BinarySearch(int arr[], const int&, const int&, const int&);
 
int ExponentialSearch(int arr[], int n, int x)
{
  if (arr[0] == x)
    return 0;
 
  int i = 1;
  while (i < n && arr[i] <= x)
    i = i*2;
  return BinarySearch(arr, i/2, std::min(i, n), x);
}
 
int BinarySearch(int arr[], const int& l, const int& r, const int& x)
{
  if (r >= l)
  {
    int mid = l + (r - l)/2;
 
    if (arr[mid] == x)
      return mid;
 
    if (arr[mid] > x)
      return BinarySearch(arr, l, mid-1, x);
 
    return BinarySearch(arr, mid+1, r, x);
  }
  return -1;
}
 
 
int main(int argc, _TCHAR* argv[])
{
  int arr[] = {1, 3, 6, 7, 9, 11, 14, 33, 36};
  int n = sizeof(arr)/ sizeof(arr[0]);
  int x = 14;
  int ret = ExponentialSearch(arr, n, x);
  if (ret == -1)
    std::cout << "Khong tim dc";
  else
     std::cout << "Gia tri can tim tai vi tri: " << ret;
  _getch();
    return 0;
}
 

+ Phù hợp cho việc tìm kiếm các mảng có số lượng phần tử lớn, hoặc phần từ tìm kiếm nằm gần đầu danh sách, (Nhanh tìm ra tập còn chứa giá trị tìm kiếm).

3. Bài Tập Về Thuật Toán Tìm Kiếm Trong C++

Sau đây là các bước triển khai thuật toán, các bước thực hiện đều được comment ở từng đoạn.

#include <bits/stdc++.h>
using namespace std;
 
//Hàm tìm kiếm nhi phân
int binarySearch(int arr[], int left, int right, int x) {
    int middle;
 
    while(left <= right) {
        // Lấy vị trí ở giữa left và right
        middle = (left + right) / 2;
 
        // Nếu phần từ ở giữa bằng x thì trả về
        // vị trí
        if (arr[middle] == x)
            return middle;
 
        // Nếu x lớn hơn phần tử ở giữa thì
        // chắc chắn nó nằm bên phải.
        // Chỉ định left phần từ ở giữa + 1
        if (x > arr[middle])
            left = middle + 1;
        else
            //Ngược lại
            right = middle - 1;
    }
 
    //Trả về -1 nếu không tìm thấy gía trị trong mảng.
    return -1;
}
int main() {
    int arr[] = {15, 20, 25, 30, 31, 44, 66};
 
    //Lấy ra độ dài của mảng
    int n = sizeof(arr) / sizeof(arr[0]);
    //Phần từ cần tìm
    int x = 25;
     
    // n -1 là vị trí cuối cùng trong mảng.
    int result = binarySearch(arr, 0, n-1, x);
 
    cout << result;
}

Khởi chạy chươn trình chúng ta sẽ nhận được kết quả là vị trí của 25 trong mảng.

Bài Tập Về Thuật Toán Tìm Kiếm Trong C++
Bài Tập Về Thuật Toán Tìm Kiếm Trong C++

The post Các Thuật Toán Tìm Kiếm Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/cac-thuat-toan-tim-kiem-trong-c/

Thuật Toán Sắp Xếp Trong C++

Sắp xếp là 1 định nghĩa cơ bản nhưng khá quan trọng đối với mỗi lập trình viên. Việc sắp xếp sẽ giúp chúng ta dễ dàng hơn trong việc giải quyết những vấn đề khác như tìm kiếm một phần tử, tìm phần tử lớn nhất, nhỏ nhất,…Trong bài viết này, hãy cùng Techacademy đi tìm hiểu kĩ hơn về thuật toán sắp xếp trong C++ nhé!

1. Sắp Xếp Mảng 1 Chiều Tăng Dần Trong C++

Cách sắp xếp mảng một chiều theo thứ tự tăng dần trong C / C++. Cách sắp xếp dãy số thực char, mảng số nguyên n nhập vào từ bàn phím.

Nếu bạn đang tìm cách sắp xếp các kí tự kiểu char, bạn cũng có thể sử dụng các này nhé!

Ở đây mình sẽ viết thành hàm cho dễ sử dụng nhé. hàm swap do mình viết ra có tác dụng đổi chỗ hai phần tử cho nhau.

// Ham doi vi tri hai phan tu
void swap(int &a, int &b){
    int temp =a;
    a=b;
    b=temp;
}
// Ham sap xep tang
void sortArrTang(int a[], int n){
    for(int i=0;i<n-1;i++)
        for(int j=i+1;j<n;j++){
            if(a[i]>a[j]){
                swap(a[i], a[j]);
            }
        }
}

Giải thích: Nếu cần sắp xếp mảng có n phần tử. Ta chỉ cần thực hiện n-1 lần chọn, bởi vì phần tử cuối cùng đã tự đúng vị trí nên trong vòng lặp for đầu tiên i<n-1.

Trong vòng lặp thứ 2: Ta sẽ chạy từ vị trí j = i+1 đến cuối mảng, tức là từ sau i đến hết mảng. Nếu có phần từ nào nhỏ hơn a[i] thì ta đổi chỗ. Như vậy sau vòng lặp đầu tiên ta sẽ tìm được phần từ nhỏ nhất của mảng. Cứ như vậy

Sắp Xếp Mảng 1 Chiều Tăng Dần Trong C++
Sắp Xếp Mảng 1 Chiều Tăng Dần Trong C++

2. Sắp Xếp Giảm Dần Trong C++

Để sắp xếp phần tử trong mảng C++ theo thứ tự giảm dần dần bằng hàm qsort, chúng ta đơn giản chỉ cần thay đổi hàm so sánh như dưới đây là xong:

int compareIntDesc(const void* a, const void* b){
    int aNum = *(int*)a;
    int bNum = *(int*)b;

    return bNum - aNum;
}

Sự khác biệt giữa 2 hàm so sánh này là ở giá trị mà nó trả về. Với hàm compareIntAsc() ở sắp xếp tăng dần thì chúng ta trả về return aNum – bNum, và với hàm compareIntDesc() ở sắp xếp giảm dần thì chúng ta trả về giá trị ngược lại là return bNum – aNum.

Và chúng ta sử dụng hàm qsort để viết chương trình sắp xếp phần tử trong mảng C++ theo thứ tự giảm dần như sau:

#include <iostream>
#include <cstdlib>
using namespace std;

/*Định nghĩa macro SIZE_OF_ARRAY để lấy độ dài (số phần tử) trong mảng chỉ định*/
#define SIZE_OF_ARRAY(array) (sizeof(array)/sizeof(array[0]))

/*Tạo hàm in phần tử trong mảng*/
void show_array(int array[], int length){
    for(short i = 0; i < length; i++)  cout << array[i] <<' ';   
    cout << endl;
}

/*Tạo hàm so sánh giảm dần sử dụng trong hàm qsort*/
int compareIntDesc(const void* a, const void* b){
    int aNum = *(int*)a;
    int bNum = *(int*)b;

    return bNum - aNum;
}

int main(){
    int array1[] = {5, 4, 7, 2, 8, 7, 3};
    int array2[] = {99, 4, 5, 2, 80, 7, 3};

    /*Sử dụng hàm qsort để sắp xếp mảng giảm dần*/
    qsort(array1, SIZE_OF_ARRAY(array1), sizeof(int), compareIntDesc);
    qsort(array2, SIZE_OF_ARRAY(array2), sizeof(int), compareIntDesc);

    /*Xem kết quả sắp xếp mảng*/
    show_array(array1, SIZE_OF_ARRAY(array1));
    show_array(array2, SIZE_OF_ARRAY(array2));

    return 0;
}

Kết quả của phép sắp xếp mảng giảm dần trong C++ như dưới đây. Bạn hãy thử chạy chương trình và kiểm tra nhé.

8 7 7 5 4 3 2 
99 80 7 5 4 3 2 
Sắp Xếp Giảm Dần Trong C++
Sắp Xếp Giảm Dần Trong C++

3. Sắp Xếp Chuỗi Tăng Dần Trong C++

Trong bài tập này chúng ta sẽ thực hiện chương trình C++ để sắp xếp các số trong mảng theo thứ tự tăng dần, đây là 1 bài tập căn bản thường gặp trong khi học ngôn ngữ C++.

Chương trình sau đây người dùng sẽ nhập vào n số, sau khi người dùng nhập xong các số đó, chương trình này sẽ sắp xếp và hiển thị chúng theo thứ tự tăng dần.

Ở đây mình đã tạo ra một hàm do người dùng định nghĩa sort_numbers_asceinating() cho mục đích sắp xếp.

Sau khi chúng ta tạo một hàm sắp xếp sort_numbers_asceinating() để thực hiện công việc sắp xếp theo thứ tự tăng dần thì chúng ta gọi nó ở hàm main() để sử dụng và hiển thị kết quả ra màn hình bằng câu lệnh cout, cin

#include <iostream>
using namespace std;
void sort_numbers_ascending(int number[], int count)
{
   int temp, i, j, k;
   for (j = 0; j < count; ++j)
   {
      for (k = j + 1; k < count; ++k)
      {
         if (number[j] > number[k])
         {
            temp = number[j];
            number[j] = number[k];
            number[k] = temp;
         }
      }
   }
   cout<<"Các số sau khi được sắp xếp tăng dần:\n";
   for (i = 0; i < count; ++i)
      cout<<"\n"<< number[i];
}
int main()
{
   int i, count, number[20];
  
   cout<<"Nhập số lương phần tử trong mảng:";
   cin>>count;
   cout<<"\nNhập giá trị cho từng phần tử trong mảng:\n";
    
   for (i = 0; i < count; ++i)
      cin>>number[i];
  
   sort_numbers_ascending(number, count);
}

Kết quả:

Sắp Xếp Chuỗi Tăng Dần Trong C++
Sắp Xếp Chuỗi Tăng Dần Trong C++

4. Hàm Sắp Xếp Trong C++

+ Bài toán sắp xếp

Thuật toán sắp xếp là lời giải của bài toán sắp xếp, vậy thì trước tiên, ta hãy tìm hiểu xem bài toán sắp xếp là gì trước đã.

Bài toán sắp xếp chắc chắn không còn xa lạ gì với mỗi chúng ta, nó là 1 trong những bài toán được bắt gặp phổ biến nhất trong thực tế. Ví dụ như sắp xếp danh sách lớp học, sắp xếp quyển sách, sắp xếp tiền… Vậy thì bài toán sắp xếp là gì?

Bài toán sắp xếp là chúng ta sẽ sắp xếp lại các phần tử của một danh sách theo chiều tăng hoặc giảm dần theo một tiêu chí nào đó của phần tử trong danh sách.

Ví dụ như bạn sắp xếp danh sách lớp học theo điểm trung bình từ cao đến thấp, sắp những quyển sách theo kích cỡ từ nhỏ đến lớn, sắp xếp những tờ tiền theo mệnh giá từ thấp đến cao…

Mục đích của việc sắp xếp chính là giúp ta có cái nhìn tổng quan hơn về những dữ liệu mà ta có, dễ dàng tìm kiếm những phần tử đứng nhất về một tiêu chí nào đó như mình đã nói trong Thuật toán kiếm tìm trong C++, hầu như đa số bài toán đều quy về bài toán tìm kiếm. Ví dụ:

Bạn có một danh sách lớp học chưa được sắp xếp, bạn muốn biết được là mức độ đề thi có khó đối với học sinh hay không, top 3 học sinh có điểm trung bình cao nhất. Vậy thì sau khi bạn thực hiện việc sắp xếp giảm theo điểm trung bình, bạn sẽ dễ dàng kiểm tra được mức độ của đề đối với học sinh là dễ hay khó thông qua việc nhìn vào đầu và cuối danh sách, đầu danh sách điểm không cao lắm và cuối danh sách điểm thấp thì chắc chắn đề này khó đối với học sinh và ngược lại.

Trong lập trình, sắp xếp không chỉ đơn giản là để tìm một hoặc nhiều phần tử đứng đầu về một tiêu chí nào đó hay để có cái nhìn tổng quan về dữ liệu, sắp xếp còn làm cơ sở cho các giải thuật nâng cao với hiệu suất cao hơn.

Ví dụ như khi thực hiện tìm kiếm, thuật toán tìm kiếm nhị phân có độ phức tạp thời gian là O(log(n)) và ổn định, nhưng thuật toán này chỉ áp dụng được với dãy đã được sắp xếp. Vậy khi này, bạn có thể thực hiện sắp xếp trước sau đó áp dụng thuật toán tìm kiếm nhị phân.

Bài toán sắp xếp chỉ đơn giản có vậy, bây giờ mình sẽ giới thiệu đến các bạn một số giải thuật tìm kiếm phổ biến nhất mà lập trình viên nào cũng nên biết. Hãy cùng bắt đầu thôi!

Lưu ý trước khi đọc bài: bạn cần có kỹ năng lập trình C++ cơ bản, hiểu về độ phức tạp của thuật toán. Trong bài viết có sử dụng từ thuật toán sắp xếp ổn định, thuật toán sắp xếp ổn định nghĩa là thứ tự của các phần tử có cùng giá trị sẽ không thay đổi so với ban đầu. Ví dụ như 1 5 3 3 4, sau khi sắp xếp cũng là 1 3 3 4 5.

+ Sắp xếp nổi bọt (Bubble Sort)

Sắp xếp nổi bọt hay bubble sort là thuật toán sắp xếp đầu tiên mà mình giới thiệu đến các bạn và cũng là thuật toán đơn giản nhất trong các thuật toán mà mình sẽ giới thiệu, ý tưởng của thuật toán này như sau:

Duyệt qua danh sách, làm cho các phần tử lớn nhất hoặc nhỏ nhất dịch chuyển về phía cuối danh sách, tiếp tục lại làm phần tử lớn nhất hoặc nhỏ nhất kế đó dịch chuyển về cuối hay chính là làm cho phần tử nhỏ nhất (hoặc lớn nhất) nổi lên, cứ như vậy cho đến hết danh sách Cụ thể các bước thực hiện của giải thuật này như sau:

  1. Gán i = 0
  2. Gán j = 0
  3. Nếu A[j] > A[j + 1] thì đối chỗ A[j] và A[j + 1]
  4. Nếu j < n – i – 1:
    • Đúng thì j = j + 1 và quay lại bước 3
    • Sai thì sang bước 5
  5. Nếu i < n – 1:
    • Đúng thì i = i + 1 và quay lại bước 2
    • Sai thì dừng lại

Thật đơn giản đúng không nào, chúng ta hãy cùng cài đặt thuật toán này trong C++ nha.

+ Sắp xếp chọn (Selection Sort)

Sắp xếp chọn hay selection sort sẽ là thuật toán thứ hai mà mình giới thiệu đến các bạn, ý tưởng của thuật toán này như sau: duyệt từ đầu đến phần tử kề cuối danh sách, duyệt tìm phần tử nhỏ nhất từ vị trí kế phần tử đang duyệt đến hết, sau đó đổi vị trí của phần tử nhỏ nhất đó với phần tử đang duyệt và cứ tiếp tục như vậy.

Cho mảng A có n phần tử chưa được sắp xếp. Cụ thể các bước của giải thuật này áp dụng trên mảng A như sau:

  1. Gán i = 0
  2. Gán j = i + 1 và min = A[i]
  3. Nếu j < n:
    • Nếu A[j] < A[min] thì min = j
    • j = j + 1
    • Quay lại bước 3
  4. Đổi chỗ A[min] và A[i]
  5. Nếu i < n – 1:
    • Đúng thì i = i + 1 và quay lại bước 2
    • Sai thì dừng lại

Đối với thuật toán sắp xếp chọn, do sử dụng 2 vòng lặp lồng vào nhau, độ phức tạp thời gian trung bình của thuật toán này là O(n2). Thuật toán sắp xếp chọn mình cài đặt là thuật toán sắp xếp không ổn định, nó còn có một phiên bản khác cải tiến là thuật toán sắp xếp chọn ổn định.

+ Sắp xếp chèn (Insertion Sort)

Sắp xếp chèn hay insertion sort là thuật toán tiếp theo mà mình giới thiệu, ý tưởng của thuật toán này như sau: ta có mảng ban đầu gồm phần tử A[0] xem như đã sắp xếp, ta sẽ duyệt từ phần tử 1 đến n – 1, tìm cách chèn những phần tử đó vào vị trí thích hợp trong mảng ban đầu đã được sắp xếp.

Giả sử cho mảng A có n phần tử chưa được sắp xếp. Các bước thực hiện của thuật toán áp dụng trên mảng A như sau:

  1. Gán i = 1
  2. Gán x = A[i] và pos = i – 1
  3. Nếu pos >= 0 và A[pos] > x:
    • A[pos + 1] = A[pos]
    • pos = pos – 1
    • Quay lại bước 3
  4. A[pos + 1] = x
  5. Nếu i < n:
    • Đúng thì i = i + 1 và quay lại bước 2
    • Sai thì dừng lại

+ Sắp xếp trộn (Merge Sort)

Sắp xếp trộn (merge sort) là một thuật toán dựa trên kỹ thuật chia để trị, ý tưởng của thuật toán này như sau: chia đôi mảng thành hai mảng con, sắp xếp hai mảng con đó và trộn lại theo đúng thứ tự, mảng con được sắp xếp bằng cách tương tự.

Giả sử left là vị trí đầu và right là cuối mảng đang xét, cụ thể các bước của thuật toán như sau:

  • Nếu mảng còn có thể chia đôi được (tức left < right)
    1. Tìm vị trí chính giữa mảng
    2. Sắp xếp mảng thứ nhất (từ vị trí left đến mid)
    3. Sắp xếp mảng thứ 2 (từ vị trí mid + 1 đến right)
    4. Trộn hai mảng đã sắp xếp với nhau

+ Sắp xếp nhanh (Quick Sort)

Sắp xếp nhanh (quick sort) hay sắp xếp phân đoạn (Partition) là là thuật toán sắp xếp dựa trên kỹ thuật chia để trị, cụ thể ý tưởng là: chọn một điểm làm chốt (gọi là pivot), sắp xếp mọi phần tử bên trái chốt đều nhỏ hơn chốt và mọi phần tử bên phải đều lớn hơn chốt, sau khi xong ta được 2 dãy con bên trái và bên phải, áp dụng tương tự cách sắp xếp này cho 2 dãy con vừa tìm được cho đến khi dãy con chỉ còn 1 phần tử.

Cụ thể áp dụng thuật toán cho mảng như sau:

  1. Chọn một phần tử làm chốt
  2. Sắp xếp phần tử bên trái nhỏ hơn chốt
  3. Sắp xếp phần tử bên phải nhỏ hơn chốt
  4. Sắp xếp hai mảng con bên trái và bên phải pivot

Phần tử được chọn làm chốt rất quan trọng, nó quyết định thời gian thực thi của thuật toán. Phần tử được chọn làm chốt tối ưu nhất là phần tử trung vị, phần tử này làm cho số phần tử nhỏ hơn trong dãy bằng hoặc sấp xỉ số phần tử lớn hơn trong dãy. Tuy nhiên, việc tìm phần tử này rất tốn kém, phải có thuật toán tìm riêng, từ đó làm giảm hiệu suất của thuật toán tìm kiếm nhanh, do đó, để đơn giản, người ta thường sử dụng phần tử chính giữa làm chốt.

5. Sắp Xếp Mảng 2 Chiều Tăng Dần Trong C++

Bắt đầu ở đây, để cho ngắn gọn bài viết. Mình sẽ chỉ đưa ra hàm con giải quyết phần đề bài của bài tập mảng 2 chiều tương ứng. Các bạn sẽ tự thêm nó vào hàm main nhé.

BT1. Viết hàm tính tổng các số chẵn trong ma trận

 
int TongCacSoChan(int a[][100], int m, int n)
{
   int sum = 0;
   for(int i = 0; i < m; i++)
      for(int j = 0; j < n; j++)
         if(a[i][j]%2==0)
            sum += a[i][j];
   return sum;
}

BT2. Viết hàm liệt kê các số nguyên tố trong ma trận, đếm các số nguyên tố có trong ma trận

 
bool SoNguyenTo(int soA)
{
    if (soA < 2)
    {
        return false;
    }
    else
    {
        for (int i = 2; i <= sqrt((float)soA); i ++)
        {
            if (soA%i==0)
            {
                return false;
            }
        }
    }
    return true;
}
 
int DemSoLuongSNT(int a[][100], int m, int n)
{
   int dem = 0;
   for(int i = 0; i < m; i++)
      for(int j = 0; j < n; j++)
         if(SoNguyenTo(a[i][j])) dem++;
   return dem;
}
 
void LietKeSNT(int a[][100], int m, int n)
{
   int dem = 0;
   for(int i = 0; i < m; i++)
      for(int j = 0; j < n; j++)
         if(SoNguyenTo(a[i][j])) printf("%d\t", a[i][j]);
}

BT3. Viết hàm xóa một dòng của ma trận. Viết hàm xóa một cột của ma trận

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
void XoaDong(int a[][100], int &m, int n, int r)
{
   for(int i=r;i<m-1;i++)
      for(int j=0;j<n;j++)
         a[i][j]=a[i+1][j];
   m--;
}
 
void XoaCot(int a[][100], int m, int &n, int c)
{
   for(int i=0;i<m;i++)
      for(int j=c;j<n-1;j++)
         a[i][j]=a[i][j+1];
   n--;
}

BT4. Viết hàm đổi chỗ 2 hàng của 1 ma trận. Viết hàm đổi chỗ 2 cột của ma trận.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
void swap(int &a, int &b)
{
   int temp = a;
   a = b;
   b = temp;
}
 
void DoiCho2Hang(int a[][100], int m, int n, int row1, int row2)
{
   if((row1>=0 && row1<m)&&(row2>=0 && row2<m))
      for(int j=0;j<n;j++)
         swap(a[row1][j],a[row2][j]);
}
 
void DoiChoHaiCot(int a[][100], int m, int n, int column1, int column2)
{
   if((column1>=0 && column1<n)&&(column2>=0 && column2<n))
      for(int i=0;i<m;i++)
         swap(a[i][column1],a[i][column2]);
}

BT5. Viết hàm tìm giá trị lớn nhất/ nhỏ nhất trên đường chéo chính của ma trận.

 
//Tìm max
int Max(int a[][100], int n)
{
   int max = a[0][0];
   for(int i = 1; i < n; i++)
      if(a[i][i] > max)
         max = a[i][i];
   return max;
}
 
//Tìm min
int Min(int a[][100], int n)
{
   int min = a[0][0];
   for(int i = 1; i < n; i++)
      if(a[i][i] < min)
         min = a[i][i];
   return min;
}
Sắp Xếp Mảng 2 Chiều Tăng Dần Trong C++
Sắp Xếp Mảng 2 Chiều Tăng Dần Trong C++

6. Các Thuật Toán Sắp Xếp Trong C++

Sắp xếp là quá trình bố trí lại các phần tử trong một tập hợp theo một trình tự nào đó nhằm mục đích giúp quản lý và tìm kiếm các phần tử dễ dàng và nhanh chóng hơn.

Tại sao phải sắp xếp?

  • Để có thể sử dụng thuật toán tìm nhị phân
  • Để thực hiện thao tác nào đó được nhanh hơn

Các phương pháp sắp xếp thông dụng:

  • Phương pháp Đổi chỗ trực tiếp (Interchange sort)
  • Phương pháp Nổi bọt (Bubble sort)
  • Phương pháp Chèn trực tiếp (Insertion sort)
  • Phương pháp Chọn trực tiếp (Selection sort)

Interchange Sort

Khái niệm nghịch thế:

  • Xét một mảng các số a[0], a[1], … a[n-1]
  • Nếu có i<j và a[i] > a[j], thì ta gọi đó là một nghịch thế

Mảng chưa sắp xếp sẽ có nghịch thế

Mảng đã có thứ tự sẽ không chứa nghịch thế

a[0] <= a[1] <=… <=[n -1]

Nhận xét:

  • Để sắp xếp một dãy số, ta có thể xét các nghịch thế có trong dãy và làm triệt tiêu dần chúng đi

Ý tưởng:

  • Xuất phát từ đầu dãy, tìm tất cả nghịch thế chứa phần tử này, triệt tiêu chúng bằng cách đổi chỗ phần tử này với phần tử tương ứng trong cặp nghịch thế
  • Lặp lại xử lý trên với các phần tử tiếp theo trong dãy
void Swap(int &a, int &b){
    int temp = a;
    a = b;
    b = temp;
}

void InterchangeSort(int a[], int n){	
    for (int i = 0; i < n - 1; i++)
        for (int j = i + 1; j < n; j++)
           if(a[i] > a[j])  //nếu có nghịch thế thì đổi chỗ
              Swap(a[i], a[j]);
}

Đánh giá:

  • Số lượng các phép so sánh xảy ra không phụ thuộc vào tình trạng của dãy số ban đầu
  • Số lượng phép hoán vị thực hiện tùy thuộc vào kết quả so sánh
Các Thuật Toán Sắp Xếp Trong C++
Các Thuật Toán Sắp Xếp Trong C++

Bubble Sort

Ý tưởng:

  • Xuất phát từ cuối dãy, đổi chỗ các cặp phần tử kế cận để đưa phần tử nhỏ hơn trong cặp phần tử đó về vị trí đầu dãy hiện hành, sau đó sẽ không xét đến nó ở bước tiếp theo
  • Ở lần xử lý thứ i có vị trí đầu dãy là i
  • Lặp lại xử lý trên cho đến khi không còn cặp phần tử nào để xét
void BubbleSort(int a[], int n){	
   for (int i = 0; i < n - 1; i++)
      for (int j = n - 1; j > i; j--)
         if(a[j] < a[j-1])
             Swap(a[j], a[j-1]);
}

Đánh giá:

  • Số lượng các phép so sánh xảy ra không phụ thuộc vào tình trạng của dãy số ban đầu
  • Số lượng phép hoán vị thực hiện tùy thuộc vào kết quả so sánh
Các Thuật Toán Sắp Xếp Trong C++
Các Thuật Toán Sắp Xếp Trong C++

Khuyết điểm:

  • Không nhận diện được tình trạng dãy đã có thứ tự hay có thứ tự từng phần
  • Các phần tử nhỏ được đưa về vị trí đúng rất nhanh, trong khi các phần tử lớn lại được đưa về vị trí đúng rất chậm\

Insertion Sort

Nhận xét:

  • Mọi dãy a[0] , a[1] ,…, a[n-1] luôn có i-1 phần tử đầu tiên a[0] , a[1] ,… , a[i-2] đã có thứ tự (i ≥ 2)

Ý tưởng chính:

  • Tìm cách chèn phần tử a[i] vào vị trí thích hợp của đoạn đã được sắp để có dãy mới a[0] , a[1] ,… , a[i-1] trở nên có thứ tự
  • Vị trí này chính là pos thỏa : a[pos-1] <= a[i ]< a[pos] (1 <= pos <= i)
void InsertionSort(int a[], int n){	
   int pos, x;
   for(int i = 1; i < n; i++){ //đoạn a[0] đã sắp
      x = a[i]; 
      pos = i;
      while(pos > 0 && x < a[pos-1]){
         a[pos] = a[pos-1];	// dời chỗ
         pos--;
      }
      a[pos] = x;
   }
}

Đánh giá:

  • Giải thuật thực hiện tất cả N-1 vòng lặp tìm pos, do số lượng phép so sánh và dời chỗ này phụ thuộc vào tình trạng của dãy số ban đầu, nên chỉ có thể ước lược trong từng trường hợp như sau:
Các Thuật Toán Sắp Xếp Trong C++
Các Thuật Toán Sắp Xếp Trong C++

Selection Sort

Nhận xét:

  • Mảng có thứ tự thì a[i] = min(a[i], a[i+1], …, a[n-1])

Ý tưởng: mô phỏng một trong những cách sắp xếp tự nhiên nhất trong thực tế:

  • Chọn phần tử nhỏ nhất trong n phần tử ban đầu, đưa phần tử này về vị trí đúng là đầu dãy hiện hành
  • Xem dãy hiện hành chỉ còn n-1 phần tử của dãy ban đầu, bắt đầu từ vị trí thứ 2; lặp lại quá trình trên cho dãy hiện hành… đến khi dãy hiện hành chỉ còn 1 phần tử
void SelectionSort(int a[], int n)
{
   int min; // chỉ số phần tử nhỏ nhất trong dãy hiện hành
   for (int  i = 0; i < n - 1; i++){
      min = i; 
      for(int j = i + 1; j < n; j++)
      	   if (a[j] < a[min])
             min = j; // ghi nhận vị trí phần tử nhỏ nhất
      if (min != i)
      	   Swap(a[min], a[i]);
   }
}

Đánh giá:

  • Ở lượt thứ i, cần (n-i) lần so sánh để xác định phần tử nhỏ nhất hiện hành
  • Số lượng phép so sánh không phụ thuộc vào tình trạng của dãy số ban đầu

Trong mọi trường hợp, số lần so sánh là:

 

Các Thuật Toán Sắp Xếp Trong C++
Các Thuật Toán Sắp Xếp Trong C++
Các Thuật Toán Sắp Xếp Trong C++
Các Thuật Toán Sắp Xếp Trong C++

7. Sắp Xếp Quicksort Trong C

Trong bài này mình sẽ giới thiệu thuật toán Quick Sort (sắp xếp nhanh), đây là thuật toán sắp xếp được xem là nhanh nhất. Chúng ta sẽ cùng nhau tìm hiểu về sắp xếp nhanh là gì? Cũng như cách thức nó hoạt động và triển khai trong C++ như thế nào.

Giải thích thuật toán

Trong phần này chúng ta có hai giai đoạn. Giai đoạn một là giai đoạn phân đoạn mảng (partition()) và giai đoạn hai là giai đoạn sắp xếp (quickSort()).

  • Chọn pivot cho mảng, ở đây mình sẽ chọn pivot là số cuối cùng của mảng.
  • Tạo hai biến là left và right để trỏ tới bên trái và bên phải của danh sách.
  • Thực hiện so sánh các phần tử với pivot. Nếu phần tử nhỏ hơn pivot thì dịch chuyển qua bên trái và ngược lại.
  • Sau khi dịch chuyển thực hiện công việc sắp xếp các phần tử trong mảng con mới, trước khi tiếp tục phân đoạn tiếp theo.

Thuật toán Quick Sort trong C++

Ở phần trên mình đã nêu ra các bước viết thuật toán. Để chi tiết hơn mình đã có chú thích rõ ràng, cụ thể trong từng dòng code trong thuật toán dưới đây. Các bạn hãy đọc thật kỹ nhé:

Hàm partition()

int partition (int arr[], int low, int high)
{
    int pivot = arr[high];    // khai báo phần tử đánh dâu pivot
    int left = low;   //khai báo biến bên trái
    int right = high - 1; //khai báo biến bên phải
    while(true){
        while(left <= right && arr[left] < pivot) left++; // tìm phần tử >= phần tử pivot trong mảng
        while(right >= left && arr[right] > pivot) right--; // tìm phần tử <= phần tử pivot trong mảng
        if (left >= right) break; // sau khi duyệt xong thì thoát khỏi vòng lặp
        swap(arr[left], arr[right]); // nếu chưa xong thì sử dụng hàm swap() để tráo đổi.
        left++; // Vì left hiện tại đã xét, nên cần tăng
        right--; // Vì right hiện tại đã xét, nên cần giảm
    }
    swap(arr[left], arr[high]);
    return left; // Trả về chỉ số sẽ dùng để chia đôi mảng
}

Hàm quicksort()

void quickSort(int arr[], int low, int high)
{
    if (low < high)
    {
        /* index là chỉ số nơi phần tử này đã đứng đúng vị trí
         và đây là phần tử chia mảng làm 2 mảng con trái & phải */
        int index = partition(arr, low, high);
  
        // Gọi đệ quy sắp xếp 2 mảng con trái và phải
        quickSort(arr, low, index - 1);
        quickSort(arr, index + 1, high);
    }
}

Hàm swap()

void swap(int &a, int &b)
{
    int t = a;
    a = b;
    b = t;
}

 Sắp Xếp Quicksort Trong C
Sắp Xếp Quicksort Trong C

8. Hàm Sắp Xếp Nổi Bọt Trong C++

Ý tưởng của sắp xếp nổi bọt

Thuật toán sắp xếp nổi bọt thực hiện sắp xếp dãy số bằng cách lặp lại công việc đổi chỗ 2 số liên tiếp nhau nếu chúng đứng sai thứ tự(số sau bé hơn số trước với trường hợp sắp xếp tăng dần) cho đến khi dãy số được sắp xếp.

Ví dụ minh họa

Giả sử chúng ta cần sắp xếp dãy số [5 1 4 2 8] này tăng dần.
Lần lặp đầu tiên:
( 5 1 4 2 8 ) –> ( 1 5 4 2 8 ), Ở đây, thuật toán sẽ so sánh hai phần tử đầu tiên, và đổi chỗ cho nhau do 5 > 1.
( 1 5 4 2 8 ) –> ( 1 4 5 2 8 ), Đổi chỗ do 5 > 4
( 1 4 5 2 8 ) –> ( 1 4 2 5 8 ), Đổi chỗ do 5 > 2
( 1 4 2 5 8 ) –> ( 1 4 2 5 8 ), Ở đây, hai phần tử đang xét đã đúng thứ tự (8 > 5), vậy ta không cần đổi chỗ.

Lần lặp thứ 2:
( 1 4 2 5 8 ) –> ( 1 4 2 5 8 )
( 1 4 2 5 8 ) –> ( 1 2 4 5 8 ), Đổi chỗ do 4 > 2
( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )
( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )
Bây giờ, dãy số đã được sắp xếp, Nhưng thuật toán của chúng ta không nhận ra điều đó ngay được. Thuật toán sẽ cần thêm một lần lặp nữa để kết luận dãy đã sắp xếp khi và khi khi nó đi từ đầu tới cuối mà không có bất kỳ lần đổi chỗ nào được thực hiện.

Lần lặp thứ 3:
( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )
( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )
( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )
( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )

Code sắp xếp nổi bọt trong C/C++

 
// Code from https://nguyenvanhieu.vn
 
#include <stdio.h>
 
void swap(int &x, int &y)
{
    int temp = x;
    x = y;
    y = temp;
}
 
// Hàm sắp xếp bubble sort
void bubbleSort(int arr[], int n)
{
    int i, j;
    bool haveSwap = false;
    for (i = 0; i < n-1; i++){
    // i phần tử cuối cùng đã được sắp xếp
        haveSwap = false;
        for (j = 0; j < n-i-1; j++){
            if (arr[j] > arr[j+1]){
                swap(arr[j], arr[j+1]);
                haveSwap = true; // Kiểm tra lần lặp này có swap không
            }
        }
        // Nếu không có swap nào được thực hiện => mảng đã sắp xếp. Không cần lặp thêm
        if(haveSwap == false){
            break;
        }
    }
}
 
/* Hàm xuất mảng */
void printArray(int arr[], int size)
{
    int i;
    for (i=0; i < size; i++)
        printf("%d ", arr[i]);
    printf("n");
}
 
// Driver program to test above functions
int main()
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr)/sizeof(arr[0]);
    bubbleSort(arr, n);
    printf("Sorted array: n");
    printArray(arr, n);
    return 0;
}

Ở đây, trong hàm bubbleSort tôi sử dụng thêm một biến haveSwap để kiểm tra tại lần lặp hiện hành có xảy ra việc đổi chỗ hai số không. Nếu không, ta có thể kết luận mảng đã sắp xếp mà không cần phải thêm một lần lặp nữa.

Kiểm tra kết quả:

 
Sorted array:
11 12 22 25 34 64 90

Đánh giá thuật toán sắp xếp nổi bọt

Độ  phức tạp thuật toán

  • Trường hợp tốt: O(n)
  • Trung bình: O(n^2)
  • Trường hợp xấu: O(n^2)

Không gian bộ nhớ sử dụng: O(1)

Hàm Sắp Xếp Nổi Bọt Trong C++
Hàm Sắp Xếp Nổi Bọt Trong C++

9. Sắp Xếp Chèn Trong C++

+ Ý tưởng thuật toán sắp xếp chèn trực tiếp

Giả sử cần sắp xếp tăng dần một danh sách có n phần tử a0, a1, a2,…,an-1.

Giả sử đoạn a[0] trong danh sách đã được sắp xếp. Bắt đầu từ phần tử thứ i=1, tức là a1. Tìm cách chèn phần tử ai vào vị trí thích hợp của đoạn đã được sắp xếp để có dãy mới a0,…,ai trở nên có thứ tự. Vị trí này chính là vị trí giữa hai phần tử ak-1 và ak thỏa ak-1 < ai < ak (0<= k <=i). Lưu ý, khi k=0 thì có nghĩa là ai cần chèn vào trước vị trí đầu tiên trong danh sách.

Lặp lại quá trình trên ở mỗi lần lặp với mỗi lần lặp thì tăng i lên 1 đến khi i<n thì dừng quá trình lặp.

Một câu hỏi đặt ra là cách chèn phần tử trong danh sách như thế nào? Các bạn hãy đọc tiếp phần 2 và 3 nhé.

+ Các bước thực hiện thuật toán

Bước 1: i = 1;//giả sử có đoạn a[0] đã được sắp xếp

Bước 2: x = a[i];

Bước 3:

Tìm vị trí pos thích hợp trong đoạn a[0] đến a[i-1] để chèn a[i] vào danh sách.

Dời chỗ các phần tử từ a[pos] đến a[i-1] sang phải 1 vị trí để dành chổ cho a[i].

Bước 4: a[pos] = x;//chèn x, có đoạn a[0],…,a[i] đã được sắp.

Bước 5: i = i+1; nếu i < n -> lặp lại bước 2, ngược lại -> Dừng.

+ Cài đặt thuật toán sắp xếp chèn trực tiếp với C++

void Insertion_Sort(int a[], int n){
   int pos, i;
   int x;//lưu giá trị a[i] tránh bị ghi đè khi dời chỗ các phần tử
   for(i=1; i<n; i++){//đoạn a[0] đã sắp xếp
      x = a[i]; pos = i-1;
      //tìm vị trí chèn x
      while((pos>=0)&&(a[pos]>x)){
                //kết hợp dời chỗ các phần tử sẽ đứng sau x trong danh sách mới
         a[pos+1] = a[pos];
         pos--;
      }
      a[pos+1] = x;//chèn x vào danh sách
   }
}
void main()
{
   int a[5] = {8, 4, 1, 6, 5};
   Insertion_Sort(a, 5);
   cout<<"Mang sau khi sap xep:"<<endl;
   for(int i=0;i<5;i++){
      cout<<a[i]<<" ";
   }
   system("pause");
}

Kết quả

Mang sau khi sap xep:
1 4 5 6 8

Đánh giá thuật toán

Trường hợp Số lần so sánh
Tốt nhất n-1
Xấu nhất n(n-1)/2

Độ phức tạp thuật toán trong trường hợp xấu nhất là O(n2).

Sắp Xếp Chèn Trong C++
Sắp Xếp Chèn Trong C++

The post Thuật Toán Sắp Xếp Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/thuat-toan-sap-xep-trong-c/

Kiểm Tra Số Nguyên Tố Trong C++

Hướng dẫn phương pháp kiểm tra số nguyên tố trong C++. Bạn sẽ học được cách tạo hàm kiểm tra số nguyên tố trong C++ cũng như cách liệt kê tất cả các số nguyên tố nhỏ hơn n bằng C++ sau bài học này.

1. Số Nguyên Tố Là Gì?

Chúng ta đều biết số nguyên tố là số tự nhiên lớn hơn 1 không phải là tích của hai số tự nhiên nhỏ hơn. Nói cách khác, số nguyên tố là những số chỉ có đúng hai ước số là 1 và chính nó.

Ví dụ, chúng ta xem xét các số 2, 3, 6 như sau:

Số 2 chỉ có đúng hai ước số là 1 và chính nó nên là số nguyên tố
Số 3 chỉ có đúng hai ước số là 1 và chính nó nên là số nguyên tố
Số 6 có 4 ước số là 1, 2 , 3 và 6, do đó nó không phải là số nguyên tố.

Số Nguyên Tố Là Gì?
Số Nguyên Tố Là Gì?

2. Kiểm Tra Số Nguyên Tố Trong C Dùng While

+ Cấu trúc vòng lặp do while

Cấu trúc vòng lặp do-while là: do { <khối lệnh> } while (<điều kiện>)

Vậy nên khi bạn chuyển từ vòng lặp for sang vòng lặp do while thì chỉ cần thay thế:

Khối lệnh trong do là những gì thực hiện trong vòng lặp for:

if(n%i==0) demuoc++; i++;

Và đừng quên theo sau nó vẫn còn biến i++ nữa nhé, mỗi lần lặp nó sẽ tăng lên 1 đơn vị.

Điều kiện trong while là: điều kiện trong for: i<=n

+ CODE SỐ NGUYÊN TỐ DO WHILE TRONG C++

#include <iostream>
using namespace std;
int main()
{
    int i=1,n,demuoc=0;  
    cout<<"nhap n = ";cin>>n;  
    do
    {
        if(n%i==0)  demuoc++; i++;
     }
    while (i<=n);     
    if (demuoc==2) cout<<"la so nguyen to";  
    else cout<<"khong la so nguyen to";
}

Kết quả khi chạy chương trình:

nhap n = 11
la so nguyen to
Dãy số nguyên tố <100: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97.

Kiểm Tra Số Nguyên Tố Trong C Dùng While
Kiểm Tra Số Nguyên Tố Trong C Dùng While

3. Dùng Đệ Quy Kiểm Tra Số Nguyên Tố

Dưới đây là một chương trình minh họa sử dụng đệ quy trong C. Bạn chú ý, trong thân hàm recurse() có lời gọi hàm tới chính nó => đó là hàm đệ quy.

 
void recurse()
{
    ... .. ...
    recurse();
    ... .. ...
}
 
int main()
{
    ... .. ...
    recurse();
    ... .. ...
}

Vậy 1 chương trình sẽ chạy như thế nào nếu có hàm đệ quy? Bạn hãy xem hình ảnh dưới đây:

 Dùng Đệ Quy Kiểm Tra Số Nguyên Tố
Dùng Đệ Quy Kiểm Tra Số Nguyên Tố

Như các bạn có thể thấy, khi một hàm đệ quy được gọi (ở ví dụ trên là hàm main gọi) thì thay vì hàm đó chỉ được thực thi 1 lần thì ở đây bản thân hàm gọi lại chính nó => Nó có thể tự chạy lại chính mình số lần bất kỳ.

The post Kiểm Tra Số Nguyên Tố Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/kiem-tra-so-nguyen-to-trong-c-2/

String Trong C++

Cùng tìm hiểu về kiểu dữ liệu string trong C++. Bạn sẽ biết khái niệm string là gì trong C++, cách khai báo string trong C++, cách khởi tạo string trong C++, cách gán string trong C++ cũng như cách truy xuất ký tự của string. Bạn cũng sẽ biết sự khác biệt giữa mảng ký tự và string trong C++ sau bài học này.

I. String Trong C++ Là Gì

Trong ngôn ngữ C++, có một kiểu dữ liệu gọi là kiểu char (kiểu ký tự) ( char là viết tắt của character). Đây là kiểu dữ liệu có kích thước bộ nhớ là 1 byte. Vì 1 byte là đơn vị nhỏ nhất nên có thể nói char là loại có kích thước bộ nhớ nhỏ nhất.

Khi chúng ta nối những ký tự (char) trong C++ lại với nhau thì sẽ được 1 chuỗi ký tự. Ví dụ như “Hello” là 1 chuỗi ký tự được tạo bởi các ký tự đơn chẳng hạn.

Và chúng ta gọi loại dữ liệu được tạo thành bởi tập hợp những ký tự đơn như vậy là kiểu dữ liệu string trong C++ hay còn gọi là chuỗi string trong C++.

Để xử lý chuỗi string trong C++, chúng ta có thể dùng đến mảng ký tự được kế thừa từ ngôn ngữ C, hoặc là sử dụng tới std::string là một trình bao bọc để xử lý chuỗi thuận tiện mới được thêm vào C++. Về phương pháp sử dụng string trong c++ bằng mảng ký tự thì Kiyoshi đã phân tích rất kỹ trong loạt bài tại chuyên đề Chuỗi trong C rồi.

String Trong C++ Là Gì
String Trong C++ Là Gì

II. Nhập String Trong C++

Hướng dẫn cách nhập xuất string trong C++. Bạn sẽ học được cách sử dụng lệnh cin, cout cũng như hàm getline() để nhập xuất string trong C++ sau bài học này.

Nhập xuất string trong C++*

Giống như việc nhập các loại dữ liệu khác, chúng ta sử dụng lệnh cin để nhập vào một string trong C++ với cú pháp sau đây:

string str;
cin >> str

Trong đó dòng đầu tiên dùng để khai báo biến str có kiểu string, và dòng thứ 2 dùng để gán dữ liệu nhập từ bàn phím vào biến str.

Tương tự, chúng ta sử dụng lệnh cout để xuất vào một string trong C++ với cú pháp sau đây:

cout >> str

Ví dụ, chúng ta viết chương trình nhập xuất string trong C++ như sau:

#include <iostream>
#include <cstring>
using namespace std;
 
int main() {
    cout << "Nhap chuoi ky tu: ";
 
    string str;
    cin >>   str;
 
    cout <<"Chuoi ky tu vua nhap: "<< str;
 
    return 0;
}

Khi đó màn hình nhập dữ liệu sẽ như sau:

Nhap chuoi ky tu: abcdd
Chuoi ky tu vua nhap: abcdd

Nhập nhiều string trong C++

Để nhập nhiều string trong C++ cách nhau bởi dấu cách, chúng ta viết các string nhập vào cách nhau bởi toán tử >>, khi dùng lệnh cin với cú pháp sau đây:

string str1, str1, str3;
cin >> str1 >> str2 >>str3 ;

Ví dụ, chúng ta viết chương trình nhập nhiều string trong C++ như sau:

#include <iostream>
#include <cstring>
using namespace std;
 
int main(){
    cout << "Nhap cac chuoi: ";
    string str1, str2, str3;
    cin >> str1 >> str2 >>str3 ;
    cout <<"Cac chuoi vua nhap: "<<str1<<' '<<str2<<' ' <<str3;
}

Khi đó màn hình nhập dữ liệu sẽ hiện ra. Chúng ta nhập từng string cách nhau bởi dấu cách từ bàn phím như sau:

Nhap cac chuoi: ab cd123 ef8
Cac chuoi vua nhap: ab cd123 ef8
Nhập String Trong C++
Nhập String Trong C++

III. Các Hàm String Trong C++

Thư viện <string> rất nhiều hàm cho phép xử lý chuỗi. Ví dụ: tìm chiều dài chuỗi, so sánh hai chuỗi, tìm kiếm/rút trích chuỗi con, nối chuỗi,…

Hàm s.length()

Trả về số lượng ký tự trong string s.

string s1 = "introduction to programming";
cout<<"length of s1 = "<<s1.length();//27

Hàm s.substr(x, y)

Rút trích một chuỗi con với chiều dài y bắt đầu tại vị trí x. Nếu không có y, một chuỗi con từ vị trí x tới cuối chuỗi sẽ được rút trích.

string s1 = "introduction to programming";
cout<<"substring has 10 characters of s1 start at 5:"<<s1.substr(5, 10);
//Kết quả: substring has 10 characters of s1 start at 5:duction to

Hàm s.find(r)

Kiểm tra chuỗi string r có xuất hiện trong chuỗi s hay không. Nếu có thì trả về vị trí bắt đầu xuất hiện chuỗi r trong chuỗi s.

string s1 = "introduction to programming";
cout<<"position of 'duc' string in s1:"<<s1.find("duc");//5

Hàm s.erase(x, n)

Xóa n ký tự bắt đầu tại vị trí x.

string s1 = "introduction to programming";
s1.erase(5, 15);
cout<<"s1 string after erase:"<<s1;//s1 string after erase:introramming

Hàm s.replace(x, n, str)

Thay thế n ký tự tại vị trí bắt đầu là x bằng chuỗi str. Lưu ý: chiều dài của str có thể lớn hơn n.

string s1 = "introduction to programming";
s1.replace(5, 10, "123123123");
cout<<"s1 string after replace:"<<s1;//s1 string after replace:intro123123123 programming

Hàm s1.compare(s2)

So sánh chuỗi s1 với s2. Giá trị trả về là -1 nếu s1 < s2, bằng 0 nếu s1 == s2, là 1 nếu s1 > s2.

string s1 = "introduction to programming";
string s2 = "c++ programming language";
cout<<"s1 compare s2:"<<s1.compare(s2);//1

Hàm s1.swap(s2)

Hoán đổi nội dung hai chuỗi.

string s1 = "introduction to programming";
string s2 = "c++ programming language";
s1.swap(s2);
cout<<"s1 after swap:"<<s1<<endl;
cout<<"s2 after swap:"<<s2;
//Kết quả:
//s1 after swap:c++ programming language
//s2 after swap:introduction to programming

Hàm s1.insert(index, s2)

Thêm chuỗi s2 vào s1 sau vị trí index.

string s1 = "introduction to programming";
string s2 = "c++ programming language";
s1.insert(10, s2);
cout<<"s1 after insert s2:"<<s1;
//Kết quả:
//s1 after insert s2:introductic++ programming languageon to programming
Các Hàm String Trong C++
Các Hàm String Trong C++

IV. Tính Độ Dài String Trong C++

Hướng dẫn cách lấy độ dài string trong C++. Bạn sẽ học được cách dùng hàm size và hàm length để lấy độ dài string trong C++ sau bài học này.

Độ dài string trong C++ là gì

Độ dài string trong C++ chính là số ký tự chứa trong string đó. Độ dài string trong C++ có thể lấy được bằng cách chia kích thước của string cho kích thước 1 phần tử trong string đó.

Độ dài string (số phần tử) = [Kích thước string] / [Kích thước kiểu char]

Do phần tử tạo nên string là các ký tự thuộc dạng char với kích thước là 1 byte, nên thông thường, chúng ta coi độ dài string trong C++ chính bằng kích thước của string đó.

Lấy độ dài string trong C++ bằng hàm size và hàm length

Do thông thường độ dài string trong C++ chính bằng kích thước của string đó, nên bằng phương pháp tính ra kích thước string thì chúng ta có thể thu về ngay độ dài của string đó.

Và để tính kích thước string trong C++, chúng ta sẽ sử dụng một trong hai hàm đã học tại bài Lấy kích thước string trong C++.

Cách lấy độ dài string trong C++ như các ví dụ sau đây:

Ví dụ 1: lấy độ dài string trong C++ bằng hàm size

#include <iostream>
#include <string>
using namespace std;

int main ()
{
    string str("Hello world");
    cout<<"Do dai string = kich thuoc string = ",str.size()<<endl;

    return 0;
}//Do dai string = kich thuoc string = 11

Ví dụ 1: lấy độ dài string trong C++ bằng hàm length

#include <iostream>
#include <string>
using namespace std;

int main ()
{
    string str("Hello world");
    cout<<"Do dai string = kich thuoc string = "<<str.length()<<endl;

    return 0;
}//Do dai string = kich thuoc string = 11

Trường hợp độ dài string khác với số ký tự có trong string

Ở phần trên chúng ta đã biết với những chuỗi được tạo thành bởi các ký tự được biểu diễn bởi 1 mã ký tự, do kích thước của các ký tự như vậy cũng là 1 byte, nên kết quả tính độ dài string bằng 2 hàm size và length cũng chính là số ký tự có trong string đó.

Tuy nhiên trong trường hợp chuỗi được tạo ra bởi các ký tự được biểu diễn bởi 2 mã ký tự như là tiếng Việt có dấu, hay kanji tiếng Nhật chẳng hạn, thì độ dài string sẽ lớn hơn số ký tự có trong string. Ví dụ:

#include <iostream>
#include <string>
using namespace std;

int main ()
{
    string str("Hà Nội");
    cout<<str.size()<<endl;

    string str2("東京");
    cout<<str2.size()<<endl;
    return 0;
}

Kết quả:

9
6

Có thể thấy rõ độ dài chuỗi trong các trường hợp này sẽ lớn hơn số ký tự có trong chuỗi đó.

Bởi vậy lúc dùng 2 hàm này để lấy số ký tự có trong string, cần hết sức chú ý trong trường hợp tồn tại ký tự được tạo thành bởi 2 mã ký tự như trên.

Tính Độ Dài String Trong C++
Tính Độ Dài String Trong C++

V. Cắt String Trong C++

Hướng dẫn phương pháp cắt string trong C++. Bạn sẽ học được cách sử dụng 3 hàm cơ bản như front(), back() và substr() để cắt string trong C++ sau bài học này.

Chúng ta có 3 phương pháp cơ bản để cắt string trong C++ như sau:

  • Hàm front(): Cắt ký tự trước tiên từ string
  • Hàm back(): Cắt ký tự cuối cùng từ string
  • Hàm substr(): Cắt một phạm vi từ string

Cắt ký tự trước tiên từ string trong C++ bằng hàm front

Hàm front là 1 hàm thành viên trong class std:string, có tác dụng tham chiếu tới ký tự đầu tiên trong chuỗi string.

Bằng cách ứng dụng hàm front(), chúng ta không những có thể cắt ra giá trị của ký tự đầu tiên trong string, mà còn có thể thay đổi giá trị của ký tự đầu tiên đó.

Chúng ta sử dụng hàm front trong C++ với cú pháp sau đây:

str.front();

Trong đó str là chuỗi string cần cắt ra ký tự ban đầu. Hàm front() sẽ trả về tham chiếu tới ký tự đầu tiên của chuỗi string, qua đó chúng ta có thể cắt lấy giá trị của ký tự đầu tiên này, hoặc là gán một ký tự khác để thay đổi nó.

Lưu ý chúng ta không dùng hàm front() cho chuỗi string trống nhé.

Ví dụ cụ thể:

#include <iostream>
#include <string>
using namespace std;
 
int main() {
    string str = "Hello World!";
    
    //Tạo tham chiếu tới vị trí ký tự đầu tiên của chuỗi string
    char frontstr = str.front();
    cout << frontstr << endl;
    
    //Thay đổi ký tự đầu tiên
    str.front() = 'K';
    cout << str << endl;
 
    return 0;
}

Kết quả:

H
Kello World!

Cắt ký tự cuối cùng từ string trong C++ bằng hàm back

Ngược lại với hàm front() chính là hàm back().

Hàm back là 1 hàm thành viên trong class std:string, có tác dụng tham chiếu đến ký tự cuối cùng trong chuỗi string.

Bằng cách ứng dụng hàm back(), chúng ta không những có thể cắt ra giá trị của ký tự đầu tiên trong string, mà còn có thể thay đổi giá trị của ký tự cuối cùng đó.

Chúng ta dùng hàm back trong C++ với cú pháp sau đây:

str.back();

Trong đó str là chuỗi string cần cắt ra ký tự cuối cùng. Hàm back() sẽ trả về tham chiếu tới ký tự cuối cùng của chuỗi string, qua đó chúng ta có thể cắt lấy giá trị của ký tự cuối cùng này, hoặc là gán một ký tự khác để thay đổi nó.

Lưu ý chúng ta không dùng hàm back() cho chuỗi string trống nhé.

Ví dụ cụ thể:

#include <iostream>
#include <string>
using namespace std;
 
int main() {
    string str = "Hello World!";
    
    //Tạo tham chiếu tới vị trí ký tự cuối cùng của chuỗi string
    char backstr = str.back();
    cout << backstr << endl;
    
    //Thay đổi ký tự cuối cùng.
    str.back() = '?';
    cout << str << endl;
 
    return 0;
}

Kết quả:

!
Hello World?

Cắt một phạm vi từ string bằng hàm substr()

Chúng ta sử dụng hàm substr() để cắt một phạm vi chỉ định từ trong string ban đầu. Ví dụ cụ thể:

#include <iostream>
#include <string>
using namespace std;
 
int main() {
    string str = "Hello World!";
    string substr = str.substr(1, 3);
    cout << substr << endl;

    string substr2 = str.substr(1,6); 
    cout << substr2 << endl;
    return 0;
}

Kết quả, các chuỗi string được cắt trong phạm vi chỉ định như sau:

ell
ello W
Cắt String Trong C++
Cắt String Trong C++

VI. Nối Chuỗi String Trong C++

Bài tập C++: Nối chuỗi trong C++

Đề bài: Viết chương trình C++ để nối 2 chuỗi trong C.

Bài tập C++ này có nhiều cách gọi: trộn hai chuỗi, nối hai chuỗi, ghép hai chuỗi hay cộng hai chuỗi, … Dù có nhiều cách gọi khác nhau đi chăng nữa thì đây cũng chỉ là một bài tập để nối ghép các ký tự của hai chuỗi để thành một chuỗi duy nhất.

Lời giải (không sử dụng hàm)

Để giải bài tập C++ này, bạn sử dụng hàm strlen() để tìm độ dài chuỗi. Sử dụng vòng lặp để lặp qua tất cả ký tự của chuỗi.

Dưới đây là chương trình C++ để giải bài tập nối chuỗi trong C++:

#include <stdio.h>
#include <string.h>
 
int main() {
   char s1[10] = "VietTuts";
   char s2[] = ".Vn";
     
   int i, j, n1, n2;
     
   n1 = strlen(s1);
   n2 = strlen(s2);
     
   j=0;
   for(i = n1; i<n1+n2; i++ ) {
      s1[i] = s2[j];
      j++;
   }
     
   s1[i] = '\0';
    
   printf("\nKet qua sau khi noi chuoi la:\n");
   printf("%s", s1);
 
   return 0;
}

Chạy chương trình C++ trên cho kết quả như sau:

Nối Chuỗi String Trong C++
Nối Chuỗi String Trong C++

Lời giải (sử dụng hàm)

Bạn có thể thực hiện nối hai chuỗi thành một chuỗi bởi sử dụng các hàm strcpy() và strcat() trong C.

Giả sử bạn cần nối hai chuỗi là str1 và str2. Bạn cần khai báo thêm một chuỗi str3 có độ dài bằng tổng độ dài của str1 và str2. Sau đó:

Sử dụng hàm strcpy(str3, str1) để sao chép chuỗi str1 vào str3.

Sử dụng hàm strcat(str3, str2) để nối chuỗi str2 với str1 vừa được sao chép.

Dưới đây là chương trình C++ để giải bài tập nối chuỗi trong C++:

#include<stdio.h>
#include<string.h>
 
int main() {
   char str1[100];
   char str2[100];
   char str3[100];
   int len;
  
   printf("Nhap chuoi 1: ");
   gets(str1);
  
   printf("Nhap chuoi 2: ");
   gets(str2);
  
   strcpy(str3, str1);
   strcat(str3, str2);
  
   printf("\nNoi chuoi: %s", str3);
  
   return (0);
}

VII. So Sánh 2 String Trong C++

Bài tập C++: So sánh hai chuỗi trong C++

Đề bài: Viết chương trình C++ để sa sánh hai chuỗi.

Yêu cầu bài tập C++ này là so sánh xem hai chuỗi có đồng nhất (giống nhau) hay không. Để giải bài tập C++ này bạn cần sử dụng vòng lặp để duyệt qua tất cả các ký tự của hai chuỗi và kiểm tra từng ký tự tương ứng với chỉ mục có giống nhau không.

Lời giải

Dưới đây là chương trình C++ để giải bài tập so sánh 2 chuỗi trong C++:

#include <stdio.h>
 
int main() {
   char s1[50];     
   char s2[50];
    
   int n = 0;
   short flag = 1;
    
    printf("Nhap chuoi s1: ");
    gets(s1);
    printf("Nhap chuoi s2: ");
    gets(s2);
     
   // so sanh chuoi s1 voi s2
   while (s1[n] != '\0') {
      if(s1[n] != s2[n]) {
         flag = 0;
         break;
      }
      n++;
   }
     
   if(flag == 1) {
      printf("Chuoi %s va chuoi %s la giong nhau.", s1, s2);
   }else {
      printf("Chuoi %s va chuoi %s la khac nhau.", s1, s2);
   }
 
   return 0;
}
So Sánh 2 String Trong C++
So Sánh 2 String Trong C++

VIII. Xóa 1 Ký Tự Trong String C++

Hướng dẫn cách xóa string trong C++. Bạn sẽ học được cách sử dụng các hàm như hàm pop_back, hàm erase để xóa string trong C++ sau bài học này.

Chúng ta có 3 phương pháp để xóa string trong C++ như sau:

  • Hàm pop_back : Xóa ký tự cuối cùng trong string
  • Hàm erase: Xóa ký tự tại vị trí chỉ định
  • Hàm erase(first, last): Xóa các ký tự trong phạm vi chỉ định

Xóa ký tự cuối cùng trong string C++ bằng pop_back

Hàm pop_back là một hàm thành viên trong class std:string, có tác dụng xóa ký tự cuối cùng trong string cũng như giảm độ dài của nó đi một đơn vị.

Cú pháp sử dụng hàm pop_back để xóa ký tự cuối cùng trong string như sau:

str.pop_back();

Trong đó str là tên string cần xóa ký tự cuối cùng. Lưu ý là ký tự cuối cùng ở đây không bao gồm ký tự kết thúc chuỗi \0.

Hàm pop_back thuộc kiểu void, do đó nó sẽ không trả giá trị. Bởi vậy nó sẽ xóa ký tự cuối cùng trong string chứ không trả về ký tự đó. Trong trường hợp muốn lấy ký tự cuối cùng trong string, hãy sử dụng tới hàm back để thay thế.

Ví dụ cụ thể:

#include <iostream>
#include <string>

int main ()
{
  std::string str ("Hello world");
  str.pop_back();
  std::cout << str;
  return 0;
}
//> Hello worl

Có thể thấy ký tự cuối cùng trong chuỗi là ký tự d đã bị xóa khỏi chuỗi ban đầu.

Xóa 1 ký tự trong string bằng hàm erase

Hàm erase là một hàm thành viên trong class std:string, có tác dụng xóa một hoặc nhiều ký tự trong string C++ cũng như làm giảm độ dài tương ứng của nó.

Để xóa 1 ký tự tại vị trí chỉ định trong string C++ bằng hàm erase chúng ta sử dụng cú pháp sau đây:

str.erase(p);

Ở đây trình lặp (iterator) là một vòng lặp có tác dụng giống như con trỏ, giúp truy cập đến các ký tự ở vị trí cụ thể trong string.

Trong trường hợp cần chỉ đến vị trí index thứ n trong string, chúng ta sẽ viết trình lặp p như sau:

str.begin() + i

Trong đó str.begin() chỉ đến vị trí đầu tiên trong string, và i là index của vị trí ký tự cần chỉ đến.

Kết hợp lại thì công thức để xóa 1 ký tự vào vị trí chỉ định trong string C++ bằng hàm erase sẽ như sau:

str.erase(str.begin() + i);

Ví dụ cụ thể:

#include <iostream>
#include <string>
using namespace std;
int main ()
{
    string str("Hello World");
    str.erase(str.begin() + 2);//Xóa ký tự ở vị trí thứ 2
    cout << str<<endl;

    str.erase(str.begin() + 6);//Xóa ký tự ở vị trí thứ 6
    cout << str;
    return 0;
}

Kết quả:

Helo World
Helo Wrld

Xóa các ký tự trong một phạm vi chỉ định trong string C++

Chúng ta sử dụng hàm erase để xóa các ký tự trong một phạm vi chỉ định trong string C++ với cú pháp sau đây:

str.erase( iterator_first, iterator_last);

Trong đó str là chuỗi ban đầu, iterator_first và iterator_last là phạm vi cần xóa được chỉ định trong trình lặp trỏ đến vị trí cần xóa trong string.

Tương tự như khi xóa 1 ký tự thì cú pháp sử dụng thực tế để xóa các ký tự trong một phạm vi chỉ định trong string C++ bằng hàm erase sẽ là:

str.erase(str.begin() + start, str.begin() + end);

Trong đó start và end là vị trí index của phạm vi xóa trong string.

Ví dụ cụ thể:

#include <iostream>
#include <string>
using namespace std;
int main ()
{
    string str("Hello World");

    //Xóa ký tự trong phạm vi index từ 1 đến 3
    str.erase(str.begin() + 1, str.begin() + 3);
    cout << str<<endl;

    //Xóa ký tự trong phạm vi index từ 2 đến 5
    str.erase(str.begin() + 2, str.begin() +5);
    cout << str;
    return 0;
}

Kết quả:

Hlo World
Hlorld
Xóa 1 Ký Tự Trong String C++
Xóa 1 Ký Tự Trong String C++

IX. Bài Tập Về String Trong C++

C++ cung cấp hai kiểu biểu diễn chuỗi như sau:

Chuỗi theo phong cách của ngôn ngữ C (C-style),

Lớp Chuỗi (String) được giới thiệu trong C/C++ chuẩn.

Chuỗi theo phong cách C

Dạng chuỗi này bắt nguồn từ ngôn ngữ C và tiếp tục được hỗ trợ trong C/C++. Chuỗi trong ngôn ngữ lập trình C thực chất là mảng một chiều của các ký tự mà kết thúc bởi một ký tự null ‘\0’.

Phần khai báo và khởi tạo dưới đây tạo ra một chuỗi bao gồm một từ “Hello”. Để giữ các giá trị null tại cuối của mảng, cỡ của mảng các ký tự bao gồm một chuỗi phải nhiều hơn số lượng các ký tự trong từ khóa “Hello”.

char loiChao[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

Nếu bạn theo quy tắc khởi tạo các chuỗi, bạn có thể viết lệnh như sau:

char loiChao[] = "Hello";

Dưới đây là phần biểu diễn ô nhớ cho đoạn chuỗi trên trong ngôn ngữ C/C++:

Bài Tập Về String Trong C++
Bài Tập Về String Trong C++

Thực tế, bạn không đặt ký tự null tại vị trí cuối cùng của biến hằng số. Bộ biên dịch C tự động thêm ‘\0’ tại ví trí cuối cùng của chuỗi khi nó khởi tạo chuỗi. Cùng thử ví dụ in ra chuỗi sau đây:

#include <iostream>

using namespace std;

int main ()
{
   char loiChao[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

   cout << "Khi gap nhau, chung ta noi: ";
   cout << loiChao << endl;

   return 0;
}

Khi đoạn code trên được biên dịch và thực hiện, kết quả in ra sẽ như sau:

Khi gap nhau, chung ta noi: Hello

Ngôn ngữ C/C++ hỗ trợ nhiều hàm đa dạng để thao tác các chuỗi kết thúc là null:

STT Hàm & Mục đích
1 strcpy(s1, s2);Sao chép chuỗi s2 cho chuỗi s1.
2 strcat(s1, s2);Nối chuỗi s2 vào cuối chuỗi s1.
3 strlen(s1);Trả về độ dài của chuỗi s1.
4 strcmp(s1, s2);Trả về 0 nếu s1 và s2 là như nhau; nhỏ hơn 0 nếu s1<s2; lớn hơn 0 nếu s1>s2.
5 strchr(s1, ch);Trả về con trỏ tới vị trí đầu tiên của ch trong s1.
6 strstr(s1, s2);Trả về con trỏ tới vị trí đầu tiên của chuỗi s2 trong chuỗi s1.

Dưới đây là ví dụ cho việc sử dụng một vài hàm bên trên:

#include <iostream>
#include <cstring>

using namespace std;

int main ()
{
   char chuoi1[10] = "Hello";
   char chuoi2[10] = "Christmas";
   char chuoi3[10];
   int  len ;

   // sao chep chuoi1 vao trong chuoi3
   strcpy( chuoi3, chuoi1);
   cout << "strcpy( chuoi3, chuoi1) : " << chuoi3 << endl;

   // noi hai chuoi: chuoi1 va chuoi2
   strcat( chuoi1, chuoi2);
   cout << "strcat( chuoi1, chuoi2): " << chuoi1 << endl;

   // tong do dai cua chuoi1 mot sau khi thuc hien noi chuoi
   len = strlen(chuoi1);
   cout << "Dung ham strlen(chuoi1) de tinh do dai chuoi1: " << len << endl;

   return 0;
}

Chạy chương trình C/C++ trên sẽ cho kết quả như hình sau:

Bài Tập Về String Trong C++
Bài Tập Về String Trong C++

Lớp String trong C/C++

Thư viện chuẩn C/C++ cung cấp một kiểu lớp String mà hỗ trợ tất cả hoạt động liên quan tới chuỗi đã đề cập ở trên, và bổ sung thêm nhiều tính năng nữa. Chúng ta sẽ học lớp này trong Thư viện chuẩn C/C++ (C++ Standard Library), nhưng lúc này, chúng ta xem xét ví dụ sau:

Lúc này, có thể bạn không hiểu ví dụ này, bởi vì chúng ta chưa bàn luận về Lớp và Đối tượng trong C/C++. Vì thế, bạn quan sát và ghi nhớ chúng tới khi bạn đã hiểu các khái niệm về Hướng đối tượng được trình bày ở chương sau đó.

#include <iostream>
#include <string>

using namespace std;

int main ()
{
   string chuoi1 = "Hello";
   string chuoi2 = "Christmas";
   string chuoi3;
   int  len ;

   // sao chep chuoi1 vao trong chuoi3
   chuoi3 = chuoi1;
   cout << "Bay gio chuoi3 la: " << chuoi3 << endl;

   // noi hai chuoi: chuoi1 va chuoi2
   chuoi3 = chuoi1 + chuoi2;
   cout << "chuoi1 + chuoi2 co ket qua la: " << chuoi3 << endl;

   // tong do dai cua chuoi3 mot sau khi thuc hien noi chuoi
   len = chuoi3.size();
   cout << "Tinh do dai voi ham chuoi3.size() :  " << len << endl;

   return 0;
}

Chạy chương trình C/C++ trên sẽ cho kết quả như hình sau:

Bài Tập Về String Trong C++
Bài Tập Về String Trong C++

X. Chuẩn Hóa String Trong C++

Chuẩn hóa chuỗi trong C

Ý tưởng chuẩn hóa chuỗi trong C như sau:

  1. Bỏ các ký tự khoảng trắng thừa ở đầu chuỗi
  2. Bỏ các ký tự khoảng trắng thừa ở cuối chuỗi
  3. Viết hoa chữ cái đầu tiên của chuỗi
  4. Bỏ qua các khoảng trắng thừa ở giữa
  5. Đưa tất cả về lowercase
  6. Viết hoa chữ cái bắt đầu từ theo ý tưởng đếm từ phía trên
 
void Correct(char str[], int length)
{
    int first = 0, last = length - 1;
    // Xóa dấu trắng đầu chuỗi
    while (first < last && str[first] == ' ')
        first++;
    // Xóa dấu trắng cuối chuỗi
    while (last > first && str[last] == ' ')
        last--;
 
    // Viết hoa chữ cái đầu tiên
    if (str[first] >= 'a' && str[first] <= 'z')
        str[first] -= 32;
    for (int i = first + 1; i <= last; i++)
    {
        // Đưa hết về chữ thường
        if (str[i] >= 'A' && str[i] <= 'Z'){
            str[i] += 32;
        }
        if (i + 1 <= last)
        {
            // Bỏ dấu trắng thừa ở giữa
            if (str[i] == str[i + 1] && str[i] == ' ')
            {
                continue;
            }
            // Nếu là chữ cái đầu mỗi từ thì viết hoa
            if (str[i] == ' ' && str[i + 1] >= 'a' && str[i + 1] <= 'z')
            {
                str[i + 1] -= 32;
            }
        }
        printf("%c", str[i]);
    }
}

Lời giải chuẩn hóa chuỗi và đếm số từ trong C

#include <stdio.h>
#include <string.h>
 
int WordCount(char str[], int length)
{
    int word = (str[0] != ' ');
    for (int i = 0; i < length - 1; i++)
    {
        if (str[i] == ' ' && str[i + 1] != ' ')
        {
            word++;
        }
    }
    return word;
}
 
void Correct(char str[], int length)
{
    int first = 0, last = length - 1;
    // Xóa dấu trắng đầu chuỗi
    while (first < last && str[first] == ' ')
        first++;
    // Xóa dấu trắng cuối chuỗi
    while (last > first && str[last] == ' ')
        last--;
 
    // Viết hoa chữ cái đầu tiên
    if (str[first] >= 'a' && str[first] <= 'z')
        str[first] -= 32;
    for (int i = first + 1; i <= last; i++)
    {
        // Đưa hết về chữ thường
        if (str[i] >= 'A' && str[i] <= 'Z'){
            str[i] += 32;
        }
        if (i + 1 <= last)
        {
            // Bỏ dấu trắng thừa ở giữa
            if (str[i] == str[i + 1] && str[i] == ' ')
            {
                continue;
            }
            // Nếu là chữ cái đầu mỗi từ thì viết hoa
            if (str[i] == ' ' && str[i + 1] >= 'a' && str[i + 1] <= 'z')
            {
                str[i + 1] -= 32;
            }
        }
        printf("%c", str[i]);
    }
}
 
int main()
{
    char str[100];
    // Nhập chuỗi
    printf("\nNhap chuoi: ");
    fgets(str, sizeof str, stdin);
    // Do sử dụng fgets nên chuỗi của chúng ta sẽ đọc cả ký tự '\n'.
    // Xóa bỏ nó đi.
    int length = strlen(str) - 1; 
    str[length] = '\0';
    printf("So tu cua \"%s\" la %d", str, WordCount(str, length));
    printf("\nXau chuan hoa la: ");
    Correct(str, length);
}

Kết quả chạy thử:

 
PS G:\c_cources\day_61> g++ .\ChuanHoa.cpp -o .\ChuanHoa
PS G:\c_cources\day_61> .\ChuanHoa.exe
 
Nhap chuoi:    LAP    trINH    khong    KHO!
So tu cua "   LAP    trINH    khong    KHO!" la 4
Xau chuan hoa la: Lap trinh khong kho
Chuẩn Hóa String Trong C++
Chuẩn Hóa String Trong C++

The post String Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/string-trong-c/

Đệ Quy Trong C++

Đệ quy trong C++ là 1 phương thức vô cùng quan trọng và là cơ sở của rất rất đa dạng thuật toán. Vì vậy, hiểu được đệ quy sẽ giúp bạn dễ dàng tiếp cận và học hỏi thêm nhiều kiến thức khác về lập trình. Trong bài viết ngày này, Techacademy sẽ chia sẻ với các bạn tất tần tật mọi thứ về đệ quy cùng với các bài tập đệ quy có lời giải chi tiết để giúp bạn hiểu rõ hơn về nó nữa đấy!

I. Đệ Quy Trong C++ Là Gì

Đệ quy trong C++ là quá trình trong đó một phương thức gọi lại chính nó một cách liên tiếp. Một hàm trong C++ gọi lại chính nó được gọi là phương thức đệ quy. Trong một hàm đệ quy sẽ gồm có điều kiện dừng và lời gọi hàm đệ quy, cú pháp cụ thể như sau:

Kiểu_trả_về   Tên_hàm (Các_tham_số)
{ 
    Điều_kiện_dừng;

    return Tên_hàm (Các_tham_số_mới) ;
    // hoặc một biểu thức có chứa lời gọi hàm.
}

Để giúp bạn dễ hình dung hơn thì dưới đây sẽ là ví dụ về hàm đệ quy giúp tính giai thừa của một số tự nhiên:

long long Giaithua(int n)
{
    if (n==0 || n==1)
       return 1;
    else
       return Giaithua(n-1) * n;
}

Giải thích thuật toán: Ở đây, điều kiện dừng chính là n=0 hoặc là n=1 thì sẽ trả về giá trị là 1 ( Do 0!=1!=1). Ngược lại, nếu n>1, hàm sẽ trả về n*Giaithua(n-1). Chẳng hạn ta cho n nhận giá trị là 3, chương trình sẽ thực thi như sau:

GiaiThua(3) 
GiaiThua(2) 
GiaiThua(1) 
return 1 
return 2*1 = 2 
return 3*2 = 6

Vậy mục đích của hàm đệ quy là chia vấn đề thành những vấn đề nhỏ hơn cho đến lúc đạt được điều kiện cơ bản. Lưu ý quan trọng khi dùng đệ quy là bắt buộc phải có điều kiện dừng, nếu không có thì sẽ làm hàm gọi hàm liên tục không có điểm dừng và dẫn đến chương trình không thể kết thúc được.

Đệ Quy Trong C++ Là Gì
Đệ Quy Trong C++ Là Gì

II. Cách Sử Dụng Đệ Quy Trong C++

Trong C++, một hàm gọi chính nó ta gọi đó là hàm đệ quy, nghĩa là trong thân hàm sẽ gọi đến chính tên hàm hiện tại và truyền đúng những tham số mà hàm đã khai báo.

Cú pháp: Cú pháp của hàm đệ quy trong C++ như sau:

HamDeQuy(){    
      HamDeQuy();  //goi lai chinh no
}

Ví dụ: Chúng ta cùng xem một ví dụ đơn giản về hàm đệ quy trong C++ đó là tính giai thừa của một số nguyên.

Trước khi giải bài toán tính giai thừa của một số nguyên trong C++ chúng ta cùng nhớ lại công thức tính giai thừa trong toán học trước đã nhé.

Theo định nghĩa giai thừa ta có:

  • 0! = 1
  • n! = 1 * 2 * 3 * … * n

Vậy là ta đã có công thức tính giai thừa của một số nguyên rồi. Nếu n = 0 thì giai thừa bằng 1. Nếu n > 0 thì giai thừa sẽ là tích từ 1 đến n. Và không có giai thừa của số âm.

Giải bằng vòng lặp For

Trước khi đi vào giải bài toán trên bằng hàm đệ quy, mình sẽ giải bằng vòng lặp for trong C++ trước nhé.

Ví dụ

#include<iostream>  
using namespace std;
int main()   
{    
    int n;
    while(true) {
        int giaithua = 1;
        cout << "Nhap so n: ";
        cin >> n;
         
        //Nhap n nho hon 0 de thoat khoi vong lap
        if(n < 0) {
            cout << "  So am khong co giai thua" << endl;
            break;
        }
 
        if ( n > 0) {
            for(int i = 1; i <=n; i++) {
                giaithua = giaithua * i;
            }
        }
        cout << "  Giai thua cua " << n << " la: " << giaithua << endl;
 
    }
    return 0;  
}

Và kết quả sau khi thực thi chương trình trên như sau:

Cách Sử Dụng Đệ Quy Trong C++
Cách Sử Dụng Đệ Quy Trong C++

Như vậy, để giải quyết bài toán giai thừa của một số bằng vòng lặp for trong C++ rất đơn giản phải không các bạn? Bây giờ mình sẽ giải bài toán giai thừa trên bằng hàm đệ quy trong C++.

Giải bằng đệ quy C++

Các bạn để ý kỹ thì thấy thuật toán tính giai thừa sẽ như sau: Giả sử cần tính 5!, lúc này quy luật của nó sẽ là.

  • 5! = 4! * 5
  • 4! = 3! * 4
  • 3! = 2! * 3
  • 2! = 1! * 2
  • 1! = 1

Thay các vế vào ta sẽ được quy luật: 5! = 1 * 2 * 3 * 4 * 5.

Giả sử ta có hàm tính gian thừa của một số tên là GT, lúc này thay vào công thức trên ta được như sau:

  • GT(5) = GT(4) * 5
  • GT(4) = GT(3) * 4
  • GT(3) = GT(2) * 3
  • GT(2) = GT(1) * 2
  • GT(1) = 1

Vậy, ta có thể áp dụng giải thuật đệ quy C++ để giải quyết, bằng cách bên trong thân hàm sẽ gọi đến chính hàm đó.

Ví dụ

#include<iostream>  
using namespace std;
int GiaiThua(int n) {
    // Trường hợp người dùng nhập
    if (n == 1)
        return 1;
    else
        return (n * GiaiThua(n - 1));
}
 
int main()   
{    
    int n;
    while(true) {
        cout << "Nhap so n: ";
        cin >> n;
        //Nhap n nho hon 0 de thoat khoi vong lap
        if(n < 0) {
            cout << "  So am khong co giai thua" << endl;
            break;
        }
        cout << "  Giai thua cua " << n << " la: " << GiaiThua(n) << endl;
    }
    return 0;  
}

Và kết quả sau khi thực thi chương trình trên như sau:

Cách Sử Dụng Đệ Quy Trong C++
Cách Sử Dụng Đệ Quy Trong C++

Đối với các bạn mới bắt đầu học lập trình thì cách giải bài toán giai thừa trên bằng vòng lặp for sẽ dễ hiểu hơn rất nhiều so với việc giải bằng hàm đệ quy. Tuy nhiên, các bạn đừng quá lo lắng, mình sẽ giải thích đoạn code cho các bạn bạn dễ hiểu.

Giả sử mình nhập n = 5 thì chương trình trên sẽ chạy như sau:

GiaiThua(5) 
   GiaiThua(4) 
      GiaiThua(3) 
         GiaiThua(2) 
            GiaiThua(1) 
               return 1 
            return 2*1 = 2 
         return 3*2 = 6 
      return 4*6 = 24 
   return 5*24 = 120

Mục đích của hàm đệ quy là chia vấn đề thành các vấn đề nhỏ hơn cho đến khi đạt được điều kiện cơ bản.

Ví dụ trong chương trình giai thừa ở trên, chúng ta đang giải quyết hàm giai thừa GiaiThua(n) bằng cách gọi hàm giai thừa nhỏ hơn GiaiThua(n-1), điều này được lặp lại liên tục cho đến khi giá trị n đạt đến điều kiện cơ sở (GiaiThua(1) = 1).

III. Đệ Quy Quay Lui Trong C++

Quay lui là 1 kĩ thuật thiết kế giải thuật dựa trên đệ quy. Ý tưởng của quay lui là tìm lời giải từng bước, mỗi bước chọn một trong số các lựa chọn khả dĩ và đệ quy. Người đầu tiên đề ra thuật ngữ này (backtrack) là nhà toán học người Mỹ D. H. Lehmer vào những năm 1950.

Tư tưởng

Dùng để giải bài toán liệt kê những cấu hình. Mỗi cấu hình được xây dựng bằng từng phần tử. Mỗi phần tử lại được chọn bằng cách thử toàn bộ các khả năng.

Các bước trong việc liệt kê cấu hình dạng X[1…n]:

  • Xét tất cả các giá trị X[1] có thể nhận, thử X[1] nhận những giá trị đó. Với mỗi giá trị của X[1] ta sẽ:
  • Xét đa số giá trị X[2] có thể nhận, lại thử X[2] cho các giá trị đó. Với mỗi giá trị X[2] lại xét khả năng giá trị của X[3]…tiếp tục như vậy cho đến bước:
  • Xét tất cả giá trị X[n] có thể nhận, thử cho X[n] nhận lần lượt giá trị đó.
  • Thông báo cấu hình tìm được.

Bản chất của quay lui là một quá trình tìm kiếm theo chiều sâu(Depth-First Search).

Mô hình thuật toán

  • Mã giả cho thuật toán quay lui.
Backtracking(k) {
   for([Mỗi phương án chọn i(thuộc tập D)]) {
      if ([Chấp nhận i]) {
         [Chọn i cho X[k]];
         if ([Thành công]) {
            [Đưa ra kết quả];
         } else {
            Backtracking(k+1);
            [Bỏ chọn i cho X[k]];
         }
      }
   }
}

Ví dụ: Trò chơi Sudoku

Sudoku là một trò chơi khá phổ biến và chắc ai cũng biết. Trò chơi như sau: có một hình vuông được chia thành 9×9 ô vuông con. Mỗi ô vuông con có giá trị trong khoảng từ 1 đến 9. Ban đầu hình vuông có một số ô vuông con cho trước (có điền sẵn số) và còn lại là trống. Hãy điền các số từ 1-9 vào các ô con lại sao cho: hàng ngang là các số khác nhau từ 1 đến 9, hàng dọc là các số khác nhau từ 1 đến 9, và mỗi khối 3×3 chính là các số khác nhau từ 1 đến 9. Sau đây là 1 ví dụ về đề bài và lời giải:

Đệ Quy Quay Lui Trong C++
Đệ Quy Quay Lui Trong C++

Áp dụng quay lui để giải bài toán sudoku. Ý tưởng: Mỗi bước tìm tập các giá trị khả dĩ để điền vào ô trống, và sau đó đệ quy để điền ô tiếp theo. Giả mã của thuật toán (ở đây chú ý mảng chỉ có kích thước 9×9×9)

void solveSudoku(int S[][9], int x, int y){
    if(y == 9){
        if(x == 8){
            printSolution(S);
            exit(0);
        } else {
            solveSudoku(S, x+1,0);
        }
    } else if(S[x][y] == 0){
        for (int k = 1; k <=9; k++){
            if(checkValid(S,x,y,k)){
                S[x][y] = k;
                solveSudoku(S, x, y+1);
                S[x][y] = 0;
            }
        }
    } else {
        solveSudoku(S,x,y+1);
    }
}

boolean checkValid(int S[][9], int x, int y, int k){
    for(int i = 0; i <9 ; i++){
        if(S[x][i] == k) return false;
    }
    for(int i = 0; i <9 ; i++){
        if(S[i][y] == k) return false;
    }
    int a = x/3, b = y/3;
    for(int i = 3*a; i < 3*a+3; i++){
        for(int j = 3*b; j < 3*b+3; j++){
            if(S[i][j] == k) return false;
        }
    }
    return true;
}

Nhận xét

Ưu điểm: Việc quay lui là thử tất cả các tổ hợp để tìm được một lời giải. Thế mạnh của phương pháp này là nhiều cài đặt tránh được việc phải thử nhiều trường hợp chưa hoàn chỉnh, nhờ đó giảm thời gian chạy.

Nhược điểm: Trong trường hợp xấu nhất độ phức tạp của quay lui vẫn là cấp số mũ. Vì nó mắc phải các nhược điểm sau:

  • Rơi vào tình trạng “thrashing”: qúa trình tìm kiếm cứ gặp phải bế tắc với cùng một nguyên nhân.
  • Thực hiện các công việc dư thừa: Mỗi lần chúng ta quay lui, chúng ta cần phải đánh giá lại lời giải trong khi đôi lúc điều đó không cần thiết.
  • Không sớm phát hiện được các khả năng bị bế tắc trong tương lai. Quay lui chuẩn, không có cơ chế nhìn về tương lai để nhận biết đc nhánh tìm kiếm sẽ đi vào bế tắc.

IV. Bài Tập Đệ Quy Quay Lui Trong C++

+ Tìm Ước Chung Lớn Nhất Và Bội Chung Nhỏ Nhất Bằng Đệ Quy

Hàm đệ quy là những hàm gọi lại chính nó. Nó hữu dụng trong các tác vụ như sắp xếp hoặc tính toán các số giai thừa… Hàm đệ quy tương ứng với khái niệm quy nạp trong toán học.

Bài tập 1. Thuật toán Euclide tìm ước chung lớn nhất

Viết chương trình tìm ước chung lớn nhất của 2 số nguyên dương a, b bằng thuật toán. Euclide được định nghĩa đệ quy như sau:

[Cài đặt:]

#include <conio.h>
#include <iostream.h>
int UCLN(int a, int b) {
if(a==b)
return a;
else if(a>b)
return UCLN(a-b,b);
else
return UCLN(a,b-a);
}
void main() {
clrscr();
int a,b;
cout<<"Nhap a = ";
cin>>a;
cout<<"Nhap b = ";
cin>>b;
cout<<"Uoc chung lon nhat cua "<<a<<" va "<<b<<" la "<<UCLN(a,b);
getch();
}

Bài tập 2. Tìm ước chung lớn nhất của n số nguyên

Viết chương trình tìm ước chung lớn nhất của n số nguyên dương 0 1 ,…, n a a được định nghĩa đệ quy như sau:

[Cài đặt:]

#include <conio.h>
#include <iostream.h>
/*Ham tra ve uoc chung lon nhat cua a va b*/
int UCLN(int a, int b) {
if(a==b)
return a;
else if(a>b)
return UCLN(a-b,b);
else
return UCLN(a,b-a);
}
/*Ham tra ve uoc chung lon nhat cua n phan tu duoc luu tru trong mang 1 chieu a*/
int UC(int a[], int n) {
if(n==1)
return a[0];
else
return UCLN(a[n-1],UC(a,n-1));
}
void main() {
clrscr();
int *a,n;
cout<<"Nhap n = ";
cin>>n;
a = new int[n];
cout<<"Nhap vao "<<n<<" phan tu\n";
for(int i=0; i<n ; i++){
cout<<"a["<<i<<"] = ";
cin>>a[i];
}
cout<<"UCLN cua "<<n<<" phan tu vua nhap la "<<UC(a,n);
getch();
}

+ Đệ Quy Fibonacci Trong C++

Chắc các bạn cũng đã biết dãy Fibonacci là gì rồi. Đó là dãy số mà số tiếp theo là tổng của hai số liền trước, ví dụ: 1, 1, 2, 3, 5, 8, 13, …. Bài viết này sẽ hướng dẫn cho các bạn cách tính số fibonacci bằng phương pháp dùng đệ quy và không dùng đệ quy.

Dùng đệ quy để tính số fibonacci

Công thức truy hồi của dãy fibonacci có dạng: f(n) = f(n-1) + f(n-2) .

Với f(1) = 1; f(2) =1;

Cách tính số Fibonacci trong C

 
#include <stdio.h>
#include <conio.h>
int Fibonacci(int n)
{
    if (n == 1 || n == 2)
        return 1;
    return Fibonacci(n - 1) + Fibonacci(n - 2);
}
int main()
{
    int n;
    printf("nhap n: ");
    scanf("%d", &n);
    printf("So Fibonacci thu %d la: %d", n, Fibonacci(n));
    return 0;
}
 
nhap n: 6
So Fibonacci thu 6 la: 8

Cách tính số Fibonacci trong C++

 
#include <iostream>
using namespace std;
int Fibonacci(int n)
{
    if (n == 1 || n == 2)
        return 1;
    return Fibonacci(n - 1) + Fibonacci(n - 2);
}
int main()
{
    int n;
    cout << "nhap n: ";
    cin >> n;
    cout << "So Fibonacci thu " << n << " la: " << Fibonacci(n);
    return 0;
}
 
nhap n: 6
So Fibonacci thu 6 la: 8

Khi đã có hệ thức truy hồi thì việc viết hàm đệ quy rất đơn giản phải không nào ? Nhưng liệu bạn có thử nhập n lớn ( cỡ 30 – 40 ) không ạ. Nếu thử rồi thì chắc các bạn cũng thấy nó chậm hơn rất nhiều. Nguyên nhân là khi tính số fibonacci thứ 5 chương trình sẽ yêu cầu tính hai số fibonacci thứ 4 và thứ 3. Nó lại tiếp tục như vậy đến khi tính được số fibonacci thứ 2 hoặc thứ 1 mới dừng lại.

Đệ Quy Fibonacci Trong C++
Đệ Quy Fibonacci Trong C++

Vậy nếu muốn chương trình của chúng ta chạy nhanh hơn thì chúng ta phải khử đệ quy. Cùng làm nhé !

Cách tính số Fibonacci không dùng đệ quy

Ý tưởng cách này là chúng ta sẽ dùng một vòng lặp để tính số Fibonacci .

  • Nếu n = 1 hoặc n = 2 thì chúng ta return 1
  • Sau đó tạo một biến i có giá trị bằng 3
  • Trong vòng while chúng ta tính a = a1 + a2
  • Sau đó gán a1 = a2 và a2 = a cứ chạy đến khi nào i = n thì dừng
 
#include <stdio.h>
int Fibonacci(int n)
{
    int a1 = 1, a2 = 1;
    if (n == 1 || n == 2)
        return 1;
    int i = 3, a;
    while (i <= n)
    {
        a = a1 + a2;
        a1 = a2;
        a2 = a;
        i++;
    }
    return a;
}
int main()
{
    int n;
    printf("nhap n: ");
    scanf("%d", &n);
    printf("So Fibonacci thu %d la: %d", n, Fibonacci(n));
    return 1;
}
 
nhap n: 40
So Fibonacci thu 40 la: 102334155

Cách tính số Fibonacci trong C++

 
#include <iostream>
using namespace std;
int Fibonacci(int n)
{
    int a1 = 1, a2 = 1;
    if (n == 1 || n == 2)
        return 1;
    int i = 3, a;
    while (i <= n)
    {
        a = a1 + a2;
        a1 = a2;
        a2 = a;
        i++;
    }
    return a;
}
int main()
{
    int n;
    cout << "nhap n: ";
    cin >> n;
    cout << "So Fibonacci thu " << n << " la: " << Fibonacci(n);
    return 1;
}
nhap n: 40
So Fibonacci thu 40 la: 102334155

Tìm 1000 số Fibonacci đầu tiên

Với code trên bạn tìm đến số Fibo thứ 50 là bị tràn số rồi. Code với số nguyên lớn dưới đây sẽ giúp bạn tính được số Fibo thứ 1000 hoặc hơn thế nữa. Có thể bạn sẽ cần đọc bài viết dưới đây trước khi tham khảo code này.

Lời giải cho chương trình in ra 1000 số Fibo đầu tiên.

 
#include <bits/stdc++.h>
using namespace std;
 
const int base = 1000000000;
const int base_digits = 9;
struct bigint
{
    vector<int> a;
    int sign;
 
    bigint() : sign(1)
    {
    }
 
    bigint(long long v)
    {
        *this = v;
    }
 
    bigint(const string &s)
    {
        read(s);
    }
 
    void operator=(const bigint &v)
    {
        sign = v.sign;
        a = v.a;
    }
 
    void operator=(long long v)
    {
        sign = 1;
        if (v < 0)
            sign = -1, v = -v;
        for (; v > 0; v = v / base)
            a.push_back(v % base);
    }
 
    bigint operator+(const bigint &v) const
    {
        if (sign == v.sign)
        {
            bigint res = v;
 
            for (int i = 0, carry = 0; i < (int)max(a.size(), v.a.size()) || carry; ++i)
            {
                if (i == (int)res.a.size())
                    res.a.push_back(0);
                res.a[i] += carry + (i < (int)a.size() ? a[i] : 0);
                carry = res.a[i] >= base;
                if (carry)
                    res.a[i] -= base;
            }
            return res;
        }
        return *this - (-v);
    }
 
    bigint operator-(const bigint &v) const
    {
        if (sign == v.sign)
        {
            if (abs() >= v.abs())
            {
                bigint res = *this;
                for (int i = 0, carry = 0; i < (int)v.a.size() || carry; ++i)
                {
                    res.a[i] -= carry + (i < (int)v.a.size() ? v.a[i] : 0);
                    carry = res.a[i] < 0;
                    if (carry)
                        res.a[i] += base;
                }
                res.trim();
                return res;
            }
            return -(v - *this);
        }
        return *this + (-v);
    }
 
    void operator*=(int v)
    {
        if (v < 0)
            sign = -sign, v = -v;
        for (int i = 0, carry = 0; i < (int)a.size() || carry; ++i)
        {
            if (i == (int)a.size())
                a.push_back(0);
            long long cur = a[i] * (long long)v + carry;
            carry = (int)(cur / base);
            a[i] = (int)(cur % base);
            //asm("divl %%ecx" : "=a"(carry), "=d"(a[i]) : "A"(cur), "c"(base));
        }
        trim();
    }
 
    bigint operator*(int v) const
    {
        bigint res = *this;
        res *= v;
        return res;
    }
 
    friend pair<bigint, bigint> divmod(const bigint &a1, const bigint &b1)
    {
        int norm = base / (b1.a.back() + 1);
        bigint a = a1.abs() * norm;
        bigint b = b1.abs() * norm;
        bigint q, r;
        q.a.resize(a.a.size());
 
        for (int i = a.a.size() - 1; i >= 0; i--)
        {
            r *= base;
            r += a.a[i];
            int s1 = r.a.size() <= b.a.size() ? 0 : r.a[b.a.size()];
            int s2 = r.a.size() <= b.a.size() - 1 ? 0 : r.a[b.a.size() - 1];
            int d = ((long long)base * s1 + s2) / b.a.back();
            r -= b * d;
            while (r < 0)
                r += b, --d;
            q.a[i] = d;
        }
 
        q.sign = a1.sign * b1.sign;
        r.sign = a1.sign;
        q.trim();
        r.trim();
        return make_pair(q, r / norm);
    }
 
    bigint operator/(const bigint &v) const
    {
        return divmod(*this, v).first;
    }
 
    bigint operator%(const bigint &v) const
    {
        return divmod(*this, v).second;
    }
 
    void operator/=(int v)
    {
        if (v < 0)
            sign = -sign, v = -v;
        for (int i = (int)a.size() - 1, rem = 0; i >= 0; --i)
        {
            long long cur = a[i] + rem * (long long)base;
            a[i] = (int)(cur / v);
            rem = (int)(cur % v);
        }
        trim();
    }
 
    bigint operator/(int v) const
    {
        bigint res = *this;
        res /= v;
        return res;
    }
 
    int operator%(int v) const
    {
        if (v < 0)
            v = -v;
        int m = 0;
        for (int i = a.size() - 1; i >= 0; --i)
            m = (a[i] + m * (long long)base) % v;
        return m * sign;
    }
 
    void operator+=(const bigint &v)
    {
        *this = *this + v;
    }
    void operator-=(const bigint &v)
    {
        *this = *this - v;
    }
    void operator*=(const bigint &v)
    {
        *this = *this * v;
    }
    void operator/=(const bigint &v)
    {
        *this = *this / v;
    }
 
    bool operator<(const bigint &v) const
    {
        if (sign != v.sign)
            return sign < v.sign;
        if (a.size() != v.a.size())
            return a.size() * sign < v.a.size() * v.sign;
        for (int i = a.size() - 1; i >= 0; i--)
            if (a[i] != v.a[i])
                return a[i] * sign < v.a[i] * sign;
        return false;
    }
 
    bool operator>(const bigint &v) const
    {
        return v < *this;
    }
    bool operator<=(const bigint &v) const
    {
        return !(v < *this);
    }
    bool operator>=(const bigint &v) const
    {
        return !(*this < v);
    }
    bool operator==(const bigint &v) const
    {
        return !(*this < v) && !(v < *this);
    }
    bool operator!=(const bigint &v) const
    {
        return *this < v || v < *this;
    }
 
    void trim()
    {
        while (!a.empty() && !a.back())
            a.pop_back();
        if (a.empty())
            sign = 1;
    }
 
    bool isZero() const
    {
        return a.empty() || (a.size() == 1 && !a[0]);
    }
 
    bigint operator-() const
    {
        bigint res = *this;
        res.sign = -sign;
        return res;
    }
 
    bigint abs() const
    {
        bigint res = *this;
        res.sign *= res.sign;
        return res;
    }
 
    long long longValue() const
    {
        long long res = 0;
        for (int i = a.size() - 1; i >= 0; i--)
            res = res * base + a[i];
        return res * sign;
    }
 
    friend bigint gcd(const bigint &a, const bigint &b)
    {
        return b.isZero() ? a : gcd(b, a % b);
    }
    friend bigint lcm(const bigint &a, const bigint &b)
    {
        return a / gcd(a, b) * b;
    }
 
    void read(const string &s)
    {
        sign = 1;
        a.clear();
        int pos = 0;
        while (pos < (int)s.size() && (s[pos] == '-' || s[pos] == '+'))
        {
            if (s[pos] == '-')
                sign = -sign;
            ++pos;
        }
        for (int i = s.size() - 1; i >= pos; i -= base_digits)
        {
            int x = 0;
            for (int j = max(pos, i - base_digits + 1); j <= i; j++)
                x = x * 10 + s[j] - '0';
            a.push_back(x);
        }
        trim();
    }
 
    friend istream &operator>>(istream &stream, bigint &v)
    {
        string s;
        stream >> s;
        v.read(s);
        return stream;
    }
 
    friend ostream &operator<<(ostream &stream, const bigint &v)
    {
        if (v.sign == -1)
            stream << '-';
        stream << (v.a.empty() ? 0 : v.a.back());
        for (int i = (int)v.a.size() - 2; i >= 0; --i)
            stream << setw(base_digits) << setfill('0') << v.a[i];
        return stream;
    }
 
    static vector<int> convert_base(const vector<int> &a, int old_digits, int new_digits)
    {
        vector<long long> p(max(old_digits, new_digits) + 1);
        p[0] = 1;
        for (int i = 1; i < (int)p.size(); i++)
            p[i] = p[i - 1] * 10;
        vector<int> res;
        long long cur = 0;
        int cur_digits = 0;
        for (int i = 0; i < (int)a.size(); i++)
        {
            cur += a[i] * p[cur_digits];
            cur_digits += old_digits;
            while (cur_digits >= new_digits)
            {
                res.push_back(int(cur % p[new_digits]));
                cur /= p[new_digits];
                cur_digits -= new_digits;
            }
        }
        res.push_back((int)cur);
        while (!res.empty() && !res.back())
            res.pop_back();
        return res;
    }
 
    typedef vector<long long> vll;
 
    static vll karatsubaMultiply(const vll &a, const vll &b)
    {
        int n = a.size();
        vll res(n + n);
        if (n <= 32)
        {
            for (int i = 0; i < n; i++)
                for (int j = 0; j < n; j++)
                    res[i + j] += a[i] * b[j];
            return res;
        }
 
        int k = n >> 1;
        vll a1(a.begin(), a.begin() + k);
        vll a2(a.begin() + k, a.end());
        vll b1(b.begin(), b.begin() + k);
        vll b2(b.begin() + k, b.end());
 
        vll a1b1 = karatsubaMultiply(a1, b1);
        vll a2b2 = karatsubaMultiply(a2, b2);
 
        for (int i = 0; i < k; i++)
            a2[i] += a1[i];
        for (int i = 0; i < k; i++)
            b2[i] += b1[i];
 
        vll r = karatsubaMultiply(a2, b2);
        for (int i = 0; i < (int)a1b1.size(); i++)
            r[i] -= a1b1[i];
        for (int i = 0; i < (int)a2b2.size(); i++)
            r[i] -= a2b2[i];
 
        for (int i = 0; i < (int)r.size(); i++)
            res[i + k] += r[i];
        for (int i = 0; i < (int)a1b1.size(); i++)
            res[i] += a1b1[i];
        for (int i = 0; i < (int)a2b2.size(); i++)
            res[i + n] += a2b2[i];
        return res;
    }
 
    bigint operator*(const bigint &v) const
    {
        vector<int> a6 = convert_base(this->a, base_digits, 6);
        vector<int> b6 = convert_base(v.a, base_digits, 6);
        vll a(a6.begin(), a6.end());
        vll b(b6.begin(), b6.end());
        while (a.size() < b.size())
            a.push_back(0);
        while (b.size() < a.size())
            b.push_back(0);
        while (a.size() & (a.size() - 1))
            a.push_back(0), b.push_back(0);
        vll c = karatsubaMultiply(a, b);
        bigint res;
        res.sign = sign * v.sign;
        for (int i = 0, carry = 0; i < (int)c.size(); i++)
        {
            long long cur = c[i] + carry;
            res.a.push_back((int)(cur % 1000000));
            carry = (int)(cur / 1000000);
        }
        res.a = convert_base(res.a, 6, base_digits);
        res.trim();
        return res;
    }
};
 
int main()
{
    bigint first, second, temp;
    first = 1;
    second = 1;
    int i = 3;
    cout << 1 << " " << first << "\n";
    cout << 2 << " " << second << "\n";
    while (i < 1000)
    {
        i++;
        temp = first + second;
        cout << i << " " << temp << "\n";
        first = second;
        second = temp;
    }
}

Dưới đây là giá trị của số Fibo thứ 1000:

 
26863810024485359386146727202142923967616609318986952340123175997617981700247881689338369654483356564191827856161443356312976673642210350324634850410377680367334151172899169723197082763985615764450078474174626

+ In Đảo Ngược Một Số Nguyên Dương N

Hướng dẫn cách tìm số đảo ngược trong C. Bạn sẽ học được cách tạo hàm tìm số đảo ngược trong C sau bài học này.

Bài toán tìm số đảo ngược trong C

Tìm số đảo ngược trong C là bài toán nhập vào một số nguyên dương n từ bàn phím. In ra số đảo ngược của số n vừa nhập.

Ví dụ chúng ta nhập số 1234 thì sẽ thu về số 4321 chẳng hạn.

Mặc dù không không có ý nghĩa về mặt ứng dụng nhưng đây là một bài toán căn bản vô cùng hay giúp chúng ta rèn luyện lập trình bằng ngôn ngữ C.

Tìm số đảo ngược trong C

Cách tìm số đảo ngược trong C rất đơn giản, chúng ta viết lại từng hàng trong số theo thứ tự ngược lại là xong.

Vậy thì chúng ta sẽ viết ngược như thế nào?

Giả sử số đã cho là n = 1234. Chúng ta có thể biểu diễn số này dưới dạng tổng các lũy thừa của 10 như sau:

n = 1x104 + 2x103 + 3x102 + 4x101

Để biểu diễn số này theo cách ngược lại, chúng ta sẽ giữ nguyên phần lũy thừa của 10 và viết ngược lại từng chữ số trong tổng như sau:

n = 4x104 + 3x103 + 2x102 + 1x101

Để tìm ra được từng chữ số trong hàng thập phân như trên, chúng ta sẽ chia lần lượt số đã cho cho các luỹ thừa của 10 để tìm phần dư là xong.

Chúng ta sẽ sử dụng vòng lặp while và viết hàm để thực hiện xử lý đảo ngược số ở trên như sau:

/*Hàm tìm số đảo ngược trong C*/
int reverse_num(int n){ 
  int reverse = 0; 
  while (n > 0) {
    reverse = reverse * 10 + n % 10;
    n /= 10;
  }
  return reverse;
}

Chúng ta có thể gọi hàm này và sử dụng trong chương trình nhập vào một số nguyên dương n từ bàn phím. In ra số đảo ngược của số n vừa nhập trong C như sau:

#include <stdio.h>

/*Hàm tìm số đảo ngược trong C*/
int reverse_num(int n){ 
  int reverse = 0; 
  while (n > 0) {
    reverse = reverse * 10 + n % 10;
    n /= 10;
  }
  return reverse;
}

int main(void){
    int n;
 
    printf(">> nhap mot so nguyen duong: ");
    scanf("%d",&n);

    int result = reverse_num(n);
    printf("So dao nguoc: %d\n",result );

    return 0;
}

Màn hình nhập liệu và kết quả tìm số đảo ngược trong C sẽ như sau:

>> nhap mot so nguyen duong: 1234
So dao nguoc: 4321

>> nhap mot so nguyen duong: 23456789
So dao nguoc: 98765432

+ In Ra Dạng Nhị Phân Của Số Nguyên Dương N

Chắc hẳn ai học về giải thuật cũng đã từng nghe qua và làm về bài toán đưa ra chuỗi nhị phân độ dài N rồi, nếu bạn mới bắt đầu học hoặc đã bỏ lỡ qua bài toán thú vị này thì cũng đừng lo, vì trong bài viết này mình sẽ giới thiệu cho tất cả các bạn về bài toán này nhé.

Chuỗi nhị phân là gì?

Khái niệm về chuỗi nhị phân

Hệ nhị phân (hay hệ đếm cơ số hai hoặc mã nhị phân) là một hệ đếm dùng hai ký tự để biểu đạt một giá trị số, bằng tổng số các lũy thừa của 2. Hai ký tự đó thường là 0 và 1, chúng thường được dùng để biểu đạt hai giá trị hiệu điện thế tương ứng (có hiệu điện thế, hoặc hiệu điện thế cao là 1 và không có, hoặc thấp là 0). Do có ưu điểm tính toán đơn giản, dễ dàng thực hiện về mặt vật lý, chẳng hạn như trên các mạch điện tử, hệ nhị phân trở thành một phần kiến tạo căn bản trong các máy tính đương thời.

Ví dụ 0, 1, 0000, 0001, 010101, 00011100 là các chuỗi nhị phân

Bài toán đưa ra chuỗi nhị phân độ dài N

Chắc hẳn ai học về giải thuật cũng đã từng nghe qua và làm về bài toán đưa ra chuỗi nhị phân độ dài N rồi, nếu bạn mới bắt đầu học hoặc đã bỏ lỡ qua bài toán thú vị này thì cũng đừng lo, vì ngay bây giờ mình sẽ giới thiệu về nó nhé.

Bài toán cụ thể như sau:

Nhập vào một số nguyên dương N (1 ≤ N ≤ 20) hãy đưa ra tất cả các chuỗi nhị phân độ dài N, một chuỗi ghi trên một dòng, các chuỗi được sắp xếp từ bé đến lớn theo thứ tự từ điển,

Ví dụ 1:

Input Output
1 0

1

Ví dụ 2:

Input Output
2 00

01

10

11

Ví dụ 3:

Input Output
3 000

001

010

011

100

101

110

111

Bài toán khá là thú vị đúng không nào, đã có ai có ý tưởng làm bài này chưa? hãy thử làm nó nhé, nếu bạn đã làm xong hoặc chưa biết làm thì cũng xem thử mình đã xử lý bài toán này bằng cách nào nhé.

Một số thuật toán đưa ra chuỗi nhị phân độ dài N

Biến đổi số thành chuỗi nhị phân

Thực chất các chuỗi nhị phân độ dài N lần lượt là biểu diễn nhị phân của các số thập phân từ 0 đến 2N-1.

Ví dụ 0 = 0(2), 1 = 1(2), 2 = 10(2), 7 = 111(2).

Việc cần làm của chúng ta rất đơn giản đó là chỉ cần chuyển đổi các số tự nhiên từ 0 đến 2N-1 sang chuỗi nhị phân là được (chú ý nhớ chèn các ký tự ‘0’ vào các chuỗi nhị phân để độ dài của chuỗi nhị phân đủ N).

Để chuyển một số tự nhiên sang chuỗi nhị phân ta có thể làm như sau:

In ra dạng nhị phân của số nguyên dương n
In ra dạng nhị phân của số nguyên dương n
string decToBin(int n){
    string ans = "";
    while (n > 0) {
        ans = char (n % 2 + '0') + ans;
        n /= 2;
    }
    while (ans.length() < N)
        ans = "0" + ans;
    return ans;
}

Ta sẽ chia N cho 2 cho đến khi kết quả bằng 0, mỗi lần chia như vậy ta lưu lại số dư của N cho 2, chuỗi nhị phân của N chính là chuỗ số dư được đọc ngược.

Source code:

#include <iostream>
#include <math.h>
 
using namespace std;
 
int N;
 
string decToBin(int n){
    string ans = "";
    while (n > 0) {
        ans = char (n % 2 + '0') + ans;
        n /= 2;
    }
    while (ans.length() < N)
        ans = "0" + ans;
    return ans;
}
 
int main(){
    cin >> N;
    int N_2 = pow(2, N);
    for (int i = 0; i < N_2; i++)
        cout << decToBin(i) << endl;
}

Đệ quy quay lui

Với bài này ta có thể dùng phương pháp đệ quy, hiểu đơn giản là chúng ta cần dùng N vòng for lồng nhau, mỗi vòng for biến chạy sẽ chạy từ 0 đến 1.

phương pháp này để các bạn luyện tập đệ quy rất tốt, nhưng mình không khuyến khích các bạn dùng đệ quy trong khi nó có thể làm theo cách khác nha.

Source code:

#include <iostream>
 
using namespace std;
 
int N;
 
int x[100];
 
void in(int x[]){
    for (int i = 1; i <= N; i++)
        cout << x[i];
    cout << endl;
}
 
void deQuy(int i){
    for (int j = 0; j <= 1; j++){
        x[i] = j;
        if (i == N)
            in(x);
        else
            deQuy(i + 1);
        
    }
}
 
int main(){
    cin >> N;
    deQuy(1);
}

Phương pháp sinh

Ta thấy rằng nếu lấy lần lượt các chuỗi nhị phân độ dài N – 1, sau đó thêm ký tự 0 hoặc 1 vào cuối chuỗi đó, ta sẽ được 2 chuỗi nhị phân độ dài N.

Ví dụ như các chuỗi nhị phân độ dài 2 có các chuỗi là “00”, “01”, “10”, “11”.

Với chuỗi “00” khi ta thêm vào cuối nó ký tự ‘0’ hoặc ‘1’ ta có chuỗi “000” và “001”.

Tương tự với chuỗi “01” ta sẽ có chuỗi “010” và “011”.

Tương tự với chuỗi “10” ta sẽ có chuỗi “100” và “101”.

Tương tự với chuỗi “11” ta sẽ có chuỗi “110” và “111”.

Như vậy chỉ cần 2 ký tự “0” và “1” ta có thể sinh ra các chuỗi nhị phân độ dài N.

Source code:

#include <iostream>
#include <math.h>
 
using namespace std;
 
int N;
 
string a[100009];
 
int main(){
    cin >> N;
    int n = 2;
    a[0] = "0";
    a[1] = "1";
    int k = 0;
    while (a[k].length() < N){
        a[n++] = a[k] + "0";
        a[n++] = a[k] + "1";
        k++;
    }
    for (int i = k; i < n; i++)
        cout << a[i] << endl;
}

Tìm chuỗi nhị phân tiếp theo

Với phương pháp này ta sẽ tìm chuỗi nhị phân tiếp theo khi biết được chuỗi nhị phân trước đó, ví dụ tiếp theo của chuỗi “101” là “110”, tiếp theo của “111” là “1000”, vậy là sao làm được như vậy.

Cách làm sẽ là với chuỗi nhị phân S, để tìm chuỗi nhị phân tiếp theo của S ta sẽ làm như sau.

– Tìm vị trí index là vị trí của bit khác ‘0’ cuối cùng của S.

– Thay thì bit thứ index từ ‘0’ thành ‘1’.

– Biến đổi tất cả bit ‘1’ thành ‘0’ từ vì trí index + 1 đến hết chuỗi.

Ví dụ:

Với chuỗi S = “10011”, thì ta có bit bằng ‘0’ cuối cùng là bit thứ 3, ta biến đổi bit 3 thành 1 và bit 4, bit 5 thành 0, ta sẽ được chuỗi nhị phân tiếp theo của S là “10100”.

Source code:

#include <iostream>
#include <math.h>
 
using namespace std;
 
int N;
 
string next(string s){
    for (int i = s.length() - 1; i >= 0; i--)
        if (s[i] == '0'){
            s[i] = '1';
            return s;
        } else
            s[i] = '0';
    return "";
}
 
int main(){
    cin >> N;
    string s = "";
    for (int i = 0; i < N; i++)
        s = "0" + s;
    for (int i = 0; i < pow(2, N); i++){
        cout << s << endl;
        s = next(s);
    }
}

+ Tính N Giai Thừa Trong C/C++ Bằng Đệ Quy Và Khử Đệ Quy

Bài viết chia sẻ thuật toán và cách tính n giai thừa trong C/C+ sử dụng hai phương pháp đệ quy và khử đệ quy. Một bài toán hay dành cho các bạn học lập trình.

Giới thiệu bài toán

Giai thừa là một bài toán kinh điển trong lập trình, nó là một bài toán mà mình tin là bất kì bạn nào mới học đều phải trải qua. Bài toán này sẽ giúp bạn hiểu được thuật toán đệ quy hoặc sử dụng thành thạo vòng lặp.

Đề bài đại loại có thể tóm tắt lại như sau: Tính n giai thừa và in kết quả ra màn hình, n nhập vào từ bàn phím.

Trước khi giải quyết bài toán, chúng ta cần hiểu định nghĩa về n! (n là một số nguyên dương): n giai thừa là tích của n số nguyên dương đầu tiên.

Công thức tổng quát: n! = n*(n-1)!

Trường hợp đặc biệt: 0! = 1

Tính N giai thừa trong CC++ bằng đệ quy và khử đệ quy
Tính N giai thừa trong CC++ bằng đệ quy và khử đệ quy

Tính giai thừa sử dụng vòng lặp

Cách tính đầu tiên này sẽ đơn giản hơn cách sử dụng đệ quy. Và nó được gọi là cách khử đệ quy bởi vì nó tránh được việc phải dùng đến đệ quy. Tùy từng trường hợp mà đệ quy và khử đệ quy có ưu điểm khác nhau.

Tư tưởng giải quyết:

  • Khai báo một biến để lưu giá trị và gán nó bằng 1: giai_thua = 1

Sử dụng vòng lặp chạy i từ 1 đến n sau đó gán: giai_thua = giai_thua*i

Code C/C++:

// giai thua su dung vong lap
int giaithualap(int n){
    int giai_thua = 1;
    for (int i = 1; i <= n; i++)
        giai_thua = giai_thua * i;
    return giai_thua;
}

Tính giai thừa sử dụng đệ quy

Để hiểu rõ hơn thuật toán này trước tiên bạn nên tìm hiểu thuật toán đệ quy.

Ở bài này, ta có công thức tổng quát n giai thừa là : n!=n*(n-1)!

Chính vì thế, ta cũng sử dụng lệnh truy hồi dựa trên công thức này.

Điều kiện dừng ở đây là khi n =1 (vì ta tính tích các số bắt đầu từ 1)

Code C/C++:

// tinh giai thua su dung de quy
int factorial(int n){
    if(n==1)
        return 1;
    return(n*factorial(n-1));
}

Đánh giá cả 2 cách: Cách sử dụng đệ quy để tính giai thừa có vẻ chuyên nghiệp hơn. Tuy nhiên cách sử dụng vòng lặp có tốc độ nhanh không kém đệ quy, thậm trí là nhanh hơn.

Trong cách bài toán thực tế, nếu để lựa chọn thì các lập trình viên sẽ sử dụng cách 1 để hạn chế ít nhất việc sử dụng đệ quy.

Chú ý: Ở đây kiểu dữ liệu của hàm mình để là kiểu int, chính vì thế chỉ có thể chạy khi n <= 19 (nếu quá thì sẽ vượt kích thước của kiểu int dẫn đến sai kết quả). Nếu bạn muốn chạy được số lớn hơn thì nên để kiểu double (max n 170).

Code full bài toán nhập N và tính đệ quy:

/*  Bai toan tinh N giai thua trong C++
    By: https://duongdinh24.com/
    github: https://github.com/duongdinh24/
*/
#include<bits/stdc++.h>
using namespace std;
// n! su dung de quy
int factorial(int n){
    if(n==1)
        return 1;
    return(n*factorial(n-1));
}
// nn! Khu de quy su dung vong lap
int giaithualap(int n){
    int giai_thua = 1;
    for (int i = 1; i <= n; i++)
        giai_thua = giai_thua * i;
    return giai_thua;
}
int main(){
    int n;
    cout<<"Nhap n: "; cin>>n;
    cout<<"Ket qua "<<n<<"!: "<<factorial(n);   // De quy
//    cout<<"Ket qua "<<n<<"!: "<<giaithualap(n);  // Khu de quy
}

The post Đệ Quy Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/de-quy-trong-c/

Ép Kiểu Trong C++

Ép kiểu trong C++ là việc gán giá trị của một biến có kiểu dữ liệu này tới biến khác có kiểu dữ liệu khác. Trong bài viết ngày hôm nay, chúng ta sẽ tìm hiểu kĩ hơn về ép kiểu trong C++ qua bai viết dưới đây nhé.

I. Ép Kiểu Trong C++ Là Gì

Toán tử ép kiểu (một cast) trong C++ là 1 toán tử đặc biệt mà làm một kiểu dữ liệu này biến đổi thành kiểu dữ liệu khác. Toán tử ép kiểu là một toán tử 1 ngôi và có cùng độ ưu tiên như bất kỳ toán tử một ngôi nào khác trong C++.

Cú pháp được dùng thường xuyên của toán tử ép kiểu trong C++ là:

(kieu_du_lieu) bieu_thuc

Ở đây, kieu_du_lieu là kiểu dữ liệu bạn muốn. Dưới đây là một số toán tử ép kiểu được hỗ trợ bởi C++:

const_cast<kieu_du_lieu> (bieu_thuc): Toán tử const_cast được sử dụng để ghi đè const và/hoặc volatile. Kiểu dữ liệu bạn muốn buộc phải giống như kiểu dữ liệu nguồn ngoại trừ sự sửa đổi của những thuộc tính const hoặc volatile trong một cast. Dạng ép kiểu này thao tác thuộc tính const của đối tượng đã truyền: hoặc được thiết lập hoặc gỡ bỏ.

dynamic_cast<kieu_du_lieu> (bieu_thuc): Toán tử dynamic_cast trong C++ thực hiện một ép kiểu tại runtime mà thẩm tra tính hợp lệ của cast. Nếu cast không thể được tạo ra, cast này thất bại và biểu thức ước lượng là null. Một toán tử dynamic_cast thực hiện các cast trên các kiểu đa hình và có thể ép một con trỏ A* thành một con trỏ B* chỉ khi đối tượng đang được trỏ tới thực sự là một đối tượng B.

reinterpret_cast<kieu_du_lieu> (bieu_thuc): Toán tử reinterpret_cast trong C++ thay đổi 1 con trỏ tới bất kỳ kiểu con trỏ khác. Nó cũng cho phép ép kiểu từ con trỏ tới một kiểu integer và ngược lại.

static_cast<kieu_du_lieu> (bieu_thuc): Toán tử static_cast trong C++ thực hiện một cast không có tính đa hình. Ví dụ, nó có thể được dùng để ép kiểu một con trỏ lớp cơ sở thành một con trỏ lớp kế thừa.

Tất cả toán tử ép kiểu trên sẽ được sử dụng trong lúc làm việc với lớp và đối tượng. Bây giờ, bạn thử ví dụ sau để hiểu toán tử ép kiểu đơn giản trong C++. Copy và paste chương trình C++ sau trong tệp test.cpp, sau đó biên dịch và chạy chương trình:

#include <iostream>
using namespace std;
 
main()
{
   double a = 15.65653;
   float b = 9.02;
   int c ;
 
   c = (int) a;
   cout << "Dong 1: Gia tri cua (int)a la: " << c << endl ;
   
   c = (int) b;
   cout << "Dong 1: Gia tri cua (int)b la: " << c << endl ;
   
   return 0;
}

Chạy chương trình C++ trên sẽ cho kết quả như hình sau:

Ép Kiểu Trong C++ Là Gì
Ép Kiểu Trong C++ Là Gì

II. Cách Ép Kiểu Dữ Liệu Trong C++


III. Ép Kiểu Class Trong C++

Ép kiểu (Casting) là quá trình convert từ kiểu dữ liệu A sang kiểu dữ liệu B. C++ có 2 kiểu casting.

Implicit conversion: việc ép kiểu được thực hiện tự động bởi compiler.

int iVar = 10;
float fVar = iVar; //fVar = 10.00

Explicit conversion: ép kiểu được thực hiện bởi lập trình viên.

int iVar = 20;
float fVar = (float)iVar / 10;

Ngôn ngữ C++ support 5 toán tử ép kiểu.
– static_cast
– const_cast
– reinterpret_cast
– dynamic_cast

static_cast

Ví dụ 1:

// static_cast_Operator.cpp  
// compile with: /LD  
class B {};  
   
class D : public B {};  
   
void f(B* pb, D* pd) 
{  
   D* pd2 = static_cast<D*>(pb);   // Not safe, D can have fields  
                                   // and methods that are not in B.  
   
   B* pb2 = static_cast<B*>(pd);   // Safe conversion, D always  
                                   // contains all of B.  
}

Trong ví dụ 1

– Line B* pb2 = static_cast<B*>(pd); an toàn vì các thuộc tính và method của class B đều thuộc class D.
– Line D* pd2 = static_cast<D*>(pb); không an toàn vì class D có các thuộc tính và method mà class B không có. Tuy nhiên, câu lệnh này không báo lỗi khi chạy runtime vì toán tử static_cast không có check runtime (khác với toán tử dynamic_cast). Điều thực sự nguy hiểm khi sử dụng *pd2 truy cập tới thuộc tinh và method chỉ thuộc lớp D và không thuộc lớp B –> Chương trình crash do ACCESS VIOLATION.

Ví dụ 2:

// static_cast_Operator_2.cpp  
// compile with: /LD /GR  
class B 
{  
public:  
   virtual void Test(){}  
};  
class D : public B {};  
   
void f(B* pb) 
{  
   D* pd1 = dynamic_cast<D*>(pb);  
   D* pd2 = static_cast<D*>(pb);  
}

Trong ví dụ 2

– Nếu pb thực sự trỏ tới đối tượng class D, thì pd1 và pd2 sẽ có cùng giá trị, pd1 và pd2 sẽ có cùng giá trị nếu pd == 0 (NULL).
– Nếu pb trỏ tới đối tượng class B, thì dynamic_static sẽ trả về 0, nhưng static_cast không thể detect được vấn đề này. Do vậy, trong trường hợp sử dụng static_cast, lập trình viên phải kiểm tra nếu pb trỏ tới đối tượng class D thì mới trả về con trỏ trỏ tới class D.

Ví dụ 3:

// static_cast_Operator_3.cpp  
// compile with: /LD /GR  
typedef unsigned char BYTE;  
   
void f() 
{  
   char ch;  
   int i = 300;  
   float f = 2.5;  
   double dbl;  
   
   ch = static_cast<char>(i);   // int to char  
   dbl = static_cast<double>(f);   // float to double  
   i = static_cast<BYTE>(ch);  
}

Trong ví dụ 3,

– Toán tửstatic_cast được sử dụng để ép kiểu cho các kiểu dữ basic. Từ int –> char, float –> double, char –> int.
– Câu lệnh “ch = static_cast(i); // int to char” gây ra mất dữ liệu vì kiểu char (1 byte) không đủ chứa kiểu int (4 bytes). Do vậy, chỉ ép kiểu từ kiểu dữ liệu NHỎ –> kiểu dữ liệu TO để tránh convert sai.

const_cast

const_cast < type-id > ( expression )

// expre_const_cast_Operator.cpp  
// compile with: /EHsc  
#include <iostream>  
   
using namespace std;  
class CCTest 
{  
public:  
   void setNumber( int );  
   void printNumber() const;  
private:  
   int number;  
};  
   
void CCTest::setNumber( int num ) { number = num; }  
   
void CCTest::printNumber() const
{  
   cout << "\nBefore: " << number;  
   const_cast< CCTest * >( this )->number--;  
   cout << "\nAfter: " << number;  
}  
   
int main() 
{  
   CCTest X;  
   X.setNumber( 8 );  
   X.printNumber(); 
   system("pause");
}
Ép Kiểu Class Trong C++
Ép Kiểu Class Trong C++

IV. Ép Kiểu Tường Minh Trong C++

Ép kiểu tường minh (Explicit type conversion) là quá trình chuyển đổi kiểu dữ liệu một cách tường minh (rõ ràng) bởi lập trình viên, sử dụng toán tử ép kiểu (casting operator) để thực hiện việc chuyển đổi.

Trong C++, có 5 cách ép kiểu tường minh:

  • C-style casts
  • Static casts
  • Const casts
  • Dynamic casts
  • Reinterpret casts

Phạm vi bài học này sẽ nói về C-style casts và Static casts, 2 cách phổ biến nhất trong C++.

Dynamic casts, Const casts và Reinterpret casts cần những kiến thức chuyên sâu hơn, nên sẽ được bỏ qua trong bài học này.

C-style casts

Trong ngôn ngữ C chuẩn, ép kiểu được thực hiện thông qua toán tử (), và tên kiểu dữ liệu cần chuyển được đặt bên trong.

Ví dụ:

#include <iostream>
using namespace std;

int main()
{
   int n1 = 3;
   int n2 = 2;
   double d = (double)n1 / n2;

   cout << d << endl;

   return 0;
}

Trong chương trình trên, trình biên dịch (compiler) chuyển đổi biến n1 từ kiểu int sang kiểu double thông qua ép kiểu tường minh C-style. Sau đó biểu thức có 2 toán hạng có kiểu double và kiểu int, nên toán hạng kiểu int sẽ được chuyển đổi ngầm định sang kiểu double. Vì vậy, kết quả của biểu thức là phép chia giữa 2 số chấm động kiểu double.

Ngôn ngữ C++ cho phép thực hiện ép kiểu tường minh C-style với cú pháp như một lời gọi hàm:

int n1 = 3;
int n2 = 2;
double d = double(n1) / n2;

Ép kiểu tường minh C-style không được trình biên dịch (compiler) kiểm tra tại thời điểm biên dịch (compile time), nên trình biên dịch sẽ không đưa ra những cảnh báo trong những trường hợp chuyển đổi không đúng

Chú ý: Tránh sử dụng ép kiểu tường minh C-style.

static_cast

Ngôn ngữ C++ có 1 toán tử ép kiểu gọi là static_cast. Bạn đã biết đến nó trong bài KIỂU KÝ TỰ TRONG C++ (Character):

#include <iostream>
#include <iomanip>	// for std::setprecision()
using namespace std;

int main()
{
   int n{ 75 };
   cout << static_cast<char>(n) << endl; // in ký tự với mã ASCII 75

   char ch{ 'K' };
   cout << static_cast<int>(ch) << endl; // in mã ASCII của ký tự 'K'
   return 0;
}

Chương trình trên sử dụng toán tử ép kiểu static cast trong C++ để in một ký tự từ một số nguyên và ngược lại.

Sử dụng static cast trong biểu thức:

Ví dụ:

#include <iostream>
using namespace std;

int main()
{
   int n1 = 3;
   int n2 = 2;
   double d = static_cast<double>(n1) / n2;

   cout << d << endl;

   return 0;
}

Ưu điểm của toán tử static_cast là nó yêu cầu compiler kiểm tra kiểu dữ liệu tại thời điểm biên dịch chương trình, hạn chế được những lỗi ngoài ý muốn.

Chú ý: Nên sử dụng toán tử static_cast thay vì ép kiểu C-style.

 Ép Kiểu Tường Minh Trong C++
Ép Kiểu Tường Minh Trong C++

V. Ép Kiểu Con Trỏ Trong C++

 

VI. Ép Kiểu Float Trong C

Chắc hẳn khi mới bắt đầu tập tành với C/C++ ai cũng gặp trường hợp phải ép kiểu int sang float và ngược lại. Vậy bài viết này mình sẽ hướng dẫn các bạn cách ép kiểu nguyên sang kiểu thực trong C/C++.

Ép từ kiểu nguyên sang kiểu thực

Ép kiểu int sang float

Có nhiều trường hợp chúng ta phải ép kiểu từ int sang float như ví dụ dưới đây

Bài toán: Nhập vào hai số nguyên dương a và b. Hãy xuất ra kết quả a/b.

#include<iostream>
 
using namespace std;
 
int main()
{
    int a, b;
    float kq;
    cout << "Nhap a: "; cin >> a;
    cout << "Nhap b: "; cin >> b;
    if (!b) {// Hoặc if(b==0)
        cout << "Mau phai khac 0 !";
        return 0;
    }
    kq = a / b;
    cout << "Ket qua la: " << kq << endl;
    return 0;
}
Nhap a: 2
Nhap b: 3
Ket qua la: 0

Trong trường hợp trên ta bắt buộc phải ép kiểu int sang float. Cú pháp ép kiểu như sau:

 
kq = (float)a / (float)b;
kq = float(a) / float(b);

Ở trên có hai cách ép kiểu nhưng mình khuyên các bạn nên dùng cách dưới, nhìn dễ hiểu hơn.

Cùng xem lại đoạn code ở trên nhé

 
#include<iostream>
 
using namespace std;
 
int main()
{
    int a, b;
    float kq;
    cout << "Nhap a: "; cin >> a;
    cout << "Nhap b: "; cin >> b;
    if (!b) {// Hoặc if(b==0)
        cout << "Mau phai khac 0 !";
        return 0;
    }
    //kq = (float)a / (float)b;
    kq = float(a) / float(b);
    cout << "Ket qua la: " << kq << endl;
    return 0;
}
Nhap a: 2
Nhap b: 3
Ket qua la: 0.666667

Ép kiểu int sang double

Cách làm cũng tương tự như trên mà thôi.

Ép từ kiểu thực sang kiểu nguyên

Ép kiểu float sang int

Ta có thể ép kiểu dữ liệu như sau:

 
#include<iostream>
 
using namespace std;
 
int main()
{
    float a = 3.5f;
    int b = int(a);
    cout << "Sau khi ep kieu: " << b << endl;
    return 0;
}
Sau khi ep kieu: 3

Lưu ý: Khi ép kiểu từ int sang float thì chỉ nhận được các chữ số trước dấu phẩy mà thôi. Chứ không làm tròn lên nhé.

Ví dụ: int a = float(8.9) thì a sẽ nhận giá trị 8 mà thôi.

Ép kiểu double sang int

Tương tự như ép kểu float sang int.

Ép Kiểu Float Trong C
Ép Kiểu Float Trong C

VII. Ép Kiểu Int Sang String Trong C++

Hướng dẫn cách chuyển int sang string trong C++. Bạn sẽ học được cách sử dụng hàm to_string() trong C++ sau bài học này.

Chúng ta có 2 phương pháp để chuyển int sang string trong C++ như sau:

  • Sử dụng hàm template (trước C++11)
  • Sử dụng hàm to_string (từ C++11)

Chuyển float và int sang string trong C++ bằng hàm template

Phương pháp đầu tiên cũng để chuyển int sang string trong C++ đó chính là sử dụng hàm template sau đây:

#include <sstream>
template <typename T> std::string tostr(const T& t)
{
    std::ostringstream os; os<<t; return os.str();
}

Hàm template trên có tác dụng chuyển đổi kiểu số bao gồm cả int lẫn float sang kiểu string. Sau khi đã khai báo template này, chúng ta có thể sử dụng nó nhiều lần trong chương trình.

Ví dụ cụ thể, chúng ta có thể chuyển float và int sang string như chương trình sau. Lưu ý chúng ta sử dụng tới hàm typeid.name() để kiểm tra kiểu của dữ liệu trước và sau lúc đổi thay bằng hàm template.

#include <iostream>
#include <sstream>  
#include <typeinfo>

using namespace std;
 
template <typename T> string tostr(const T& t)
{
    ostringstream os; os<<t; return os.str();
}

 
int main() {
    int a = 123;
    float b = 3.45;

    cout << "Kieu cua a: "<< typeid(a).name() <<endl;
    cout << "Kieu cua b: "<<typeid(b).name() <<endl;
    
    //Kiểu sau khi thay đổi
    cout << "Kieu thay doi cua a: "<< typeid(tostr(123)).name() <<endl;
    cout << "Kieu thay doi cua b: "<<typeid(tostr(3.1415)).name() <<endl;
    return 0;
}

Kết quả, hai biến a và b với kiểu int và float đã được chuyển sang kiểu string bằng hàm tostr như sau:

Kieu cua a: i
Kieu cua b: f
Kieu thay doi cua a: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Kieu thay doi cua b: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

Chuyển int sang string trong C++ bằng hàm to_string

Để dùng hàm template ở trên, chúng ta đều cần khai báo template ở đầu mỗi chương trình, và chuyện này thật là rườm rà phải không nào?

Đó là lý do mà từ phiên bản C++11 trở đi, song hành cùng với hàm template ở trên thì các nhà phát triển C++ đã thêm vào 1 hàm mới có chức năng tương tự nhưng lại có thể sử dụng dễ dàng hơn, đó chính là hàm to_string.

Hàm to_string là 1 hàm được thêm vào từ phiên bản C++11, có tác dụng chuyển số sang string trong C++. Hàm to_string có thể chuyển tất cả các định dạng kiểu số như int, float, double sang string trong C++ với cú pháp đơn giản và ngắn gọn.

Chúng ta sử dụng hàm to_string() trong C++ với cú pháp sau đây:

to_string(num);

Trong đó num là số cần chuyển sang kiểu chuỗi string, và num có thể ở bất kiểu số nào, ví dụ như int, float, double hay short chẳng hạn.

Ví dụ cụ thể, chúng ta có thể chuyển int sang string trong C++ bằng hàm to_string và kiểm tra kiểu trước và sau khi chuyển đổi bằng hàm typeid.name() như sau:

#include <iostream>
#include <typeinfo>

using namespace std;

int main() {
    int a = 123;
    float b = 3.45;

    cout << "Kieu cua a: "<< typeid(a).name() <<endl;
    cout << "Kieu cua b: "<<typeid(b).name() <<endl;
    
    //Kiểu sau khi thay đổi
    cout << "Kieu thay doi cua a: "<< typeid(to_string(123)).name() <<endl;
    cout << "Kieu thay doi cua b: "<<typeid(to_string(3.1415)).name() <<endl;
    return 0;
}

Kết quả, hai biến a và b với kiểu int và float đã được chuyển sang kiểu string bằng hàm to_string như sau:

Kieu cua a: i
Kieu cua b: f
Kieu thay doi cua a: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Kieu thay doi cua b: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Ép Kiểu Int Sang String Trong C++
Ép Kiểu Int Sang String Trong C++

VIII. Ép Kiểu String Sang Char Trong C+

 

IX. Ép Kiểu Int Sang Char Trong C++

X. Phân Loại Ép Kiểu Trong C++

Phân loại ép kiểu trong C++

Trong C++, có hai loại ép kiểu dữ liệu:

  1. Nới rộng (widening): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin.
  2. Thu hẹp (narrowwing): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin

1. Nới rộng (widening)

Phân Loại Ép Kiểu Trong C++
Phân Loại Ép Kiểu Trong C++

Nới rộng (widening): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin. Ví dụ chuyển từ int sang float. Chuyển kiểu loại này có thế được thực hiện ngầm định bởi trình biên dịch.

Ví dụ:

#include <iostream>
 
using namespace std;
  
int main() {
    int i = 100;
    long l = i;    // khong yeu cau chi dinh ep kieu
    float f = l;   // khong yeu cau chi dinh ep kieu
    cout << "Gia tri Int: " << i << endl;
    cout << "Gia tri Long: " << l << endl;
    cout << "Gia tri Float: " << f << endl;
    return 0;
}

Kết quả:

Giá trị Int: 100
Giá trị Long: 100
Giá trị Float:  100

2.Thu hẹp (narrowwing)

Phân Loại Ép Kiểu Trong C++
Phân Loại Ép Kiểu Trong C++

Thu hẹp (narrowwing): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin như ví dụ ở trên. Chuyển kiểu loại này không thể thực hiện ngầm định bởi trình biên dịch, người dùng phải thực hiện chuyển kiểu tường minh.

Ví dụ:

#include <iostream>
 
using namespace std;
  
int main() {
    double d = 100.04;
    long l = (long) d; // yeu cau chi dinh kieu du lieu (long)
    int i = (int) l; // yeu cau chi dinh kieu du lieu (int)
    cout << "Gia tri Double: " << d << endl;
    cout << "Gia tri Long: " << l << endl;
    cout << "Gia tri Int: " << i << endl;
    return 0;
}

Kết quả:

Giá trị Double: 100.04
Giá trị Long: 100
Giá trị Int:  100

The post Ép Kiểu Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/ep-kieu-trong-c/

Căn Bậc 2 Trong C++

Trong bài viết này chúng ta sẽ tìm hiểu về hàm sqrt() trong C++. Đây là hàm được sử dụng rất nhiều trong các ngôn ngữ lập trình khi giải các bài toán.

I. Căn Bậc 2 Trong C++ Là Gì ?

Hàm sqrt() là hàm có sẵn trong thư viện math, vì vậy trước khi sử dụng nó hãy khai báo thư viện đã nhé: #include<math.h>

Hàm sqrt() là hàm được sử dụng để tính căn bậc hai của một số. Tham số truyền vào nó là một số bất kỳ và giá trị trả về là căn bậc hai của tham số đó.

Cú pháp:

sqrt(x)

Trong đó x là một số truyền vào bất kỳ, có thể là số nguyên hoặc số thực.

Căn Bậc 2 Trong C++ Là Gì ?
Căn Bậc 2 Trong C++ Là Gì ?

II. Hàm Tính Căn Bậc 2 Trong C++

Hôm nay Techacademy quay lại cùng với một bài toán đơn giản mà phức tạp. Chắc hẳn khi học lập trình bạn nào cũng đã từng tính căn bậc hai của một số. Chắc không ai quên được tên hàm nó chính là hàm sqrt(). Nhưng các bạn đã bao giờ tự hỏi làm sao để có thể tự viết hàm sqrt() này chưa? Hãy cũng mình đi tìm giải pháp trong bài viết này nhé.

Tính căn bậc hai sử dụng hàm trong C/C++

 
// Code from https://techacademy.edu.vn
#include <stdio.h>
#include <math.h>
 
int main()
{
    int x;
    printf("Input x: ");
    scanf("%d", &x);
    printf("Sqrt of %d = %f\n", x, sqrt(x));
}

Chạy demo:

 
Input x: 5
Sqrt of 5 = 2.236068

Tìm căn bậc hai của một số không dùng hàm thì sao?

Ý tưởng tìm căn bậc hai:

  • Khai báo 1 epsilon đặt sai số chấp nhận, vì căn bậc hai của một số có thể là số thập phân vô hạn
  • Khởi tạo kết quả bằng 1.0

Nếu kết quả có sai số cao hơn epsilon, cập nhật lại kết quả theo công thức

 
result = (number/result - result) / 2 + result;
  • Mình sẽ thu hẹp dần giới hạn trên và giới hạn dưới của kết quả, lấy trung bình hiệu khoảng cách giới hạn đó để cập nhật kết quả. Điều này luôn đảm bảo rằng giới hạn trên dưới sẽ bị thu hẹp nhưng sẽ luôn bao bọc đáp án.
  • Nếu kết quả có sai số nhỏ hơn EPSILON thì dừng lại và lấy kết quả đó làm đáp án.

Ví dụ: Bạn cần tính căn bậc 2 của 5.

  • Ta khởi tạo kết quả là 1.0. Kết quả này dĩ nhiên không đúng rồi, nên đáp số sẽ nằm trong khoảng 1.0 và 5/1.0 = 5.0.
  • Lấy một nửa hiệu khoảng 1.0 đến 5.0 là 1.0 + (5.0 – 1.0)/2 được 3.0. Nhưng 3.0 lớn hơn kết quả thực(bình phương là biết, trong code thì sai số để check),
  • Lại lấy nửa hiệu khoảng từ 5/3.0 đến 3.0 cộng vào kết quả hiện tại(3.0) = 3.0 + (5/3.0 – 3.0) = 2.33…
  • Cứ làm tiếp tục như vậy cho tới khi sai số nhỏ hơn EPSILON

Code đầy đủ cho ý tưởng này là

 
// Code from https://techacademy.edu.vn
 
#include <stdio.h>
#include <math.h>
 
#define EPSILON 0.0001f
 
double mySqrt(int number)
{
    double result = 1.0f;
    while (fabs(result * result - number) / number >= EPSILON)
        result = (number / result  - result) / 2 + result;
    return result;
}
 
int main()
{
    int x;
    printf("Input x: ");
    scanf("%d", &x);
    printf("Sqrt of %d = %1.9f\n", x, mySqrt(x));
    return 0;
}

Và chạy thử xem sao

 
Input x: 5
Sqrt of 5 = 2.236069
Hàm Tính Căn Bậc 2 Trong C++
Hàm Tính Căn Bậc 2 Trong C++

III. Ví Dụ Về Căn Bậc 2 Trong C++

Trong phần này mình sẽ thực hiện hai ví dụ sử dụng hàm sqrt() để các bạn có thể so sánh kết quả nhé.

Ví dụ 1: Ở ví dụ này mình sẽ khai báo và khởi tạo giá trị x = 9, sau đó gọi hàm sqrt() để tính căn bậc hai của x.

#include <iostream>
#include <math.h>
using namespace std;
 
int main()
{
  //khai báo và khởi tạo giá trị x = 9
  double x = 9, result;
  //gọi hàm sqrt() để tính căn bậc hai của x
    result = sqrt(x);
  //hiển thị kết quả ra màn hình
    cout << "Căn bậc hai của " << x << " là " << result << endl;
   
  cout<<"\n--------------------------------------------\n";
  cout<<"Chương trình này được đăng tại techacademy.edu.vn";
}

Kết quả:

Ví Dụ Về Căn Bậc 2 Trong C++
Ví Dụ Về Căn Bậc 2 Trong C++

Ví dụ 2: Ở ví dụ này mình sẽ thay đổi giá trị của x thành 125, cùng xem kết quả nhé.

#include <iostream>
#include <math.h>
using namespace std;
 
int main()
{
  //khai báo và khởi tạo giá trị x = 125
  double x = 125, result;
  //gọi hàm sqrt() để tính căn bậc hai của x
    result = sqrt(x);
  //hiển thị kết quả ra màn hình
    cout << "Căn bậc hai của " << x << " là " << result << endl;
   
  cout<<"\n--------------------------------------------\n";
  cout<<"Chương trình này được đăng tại Freetuts.net";
}

Kết quả:

Ví Dụ Về Căn Bậc 2 Trong C++
Ví Dụ Về Căn Bậc 2 Trong C++

IV. Căn Bậc N Trong C

Bài này mình sẽ hướng dẫn các bạn mới học lập trình cách để tính căn bậc n với n là một số bất kỳ nhé, nhưng n >= 2 nha. Chỉ là chỉ lại giúp các bạn nhớ lại toán học thôi ^^.

Một số thắc mắc của các bạn sinh viên:

  • Tình hình là em đang bí trong cách tính căn bậc n của 1 số. Thực tình em mới học môn này được 2 tuần nên thực tình ko biết giải quyết thế nào, ai biết phép toán của nó ra sao thì giúp em với?
  • Trong C chỉ có hàm sqrt để tính căn bậc 2, vậy nếu em muốn tính căn bậc 3 hay căn bậc n của một số thì làm thế nào ạ?

Tính căn bậc 3 trong C/C++

Để tính căn bậc 3 trong C/C++, bạn sử dụng công thức toán học sau. Công thức này đúng với căn bậc n luôn nhé. Bạn có thể áp dụng để tính căn bậc n của số bất kỳ:

Như vậy, với một số x = 8 chẳng hạn, bạn muốn tính căn bậc 3 của 8. Khi đó, trong công thức trên x = 8, n = 3, m = 1. Vậy ta sẽ phải tính x1/3

Để tính ax trong C/C++, chúng ta có thể sử dụng hàm pow() có cú pháp như sau:

 
double pow (double base, double exponent);

Ví dụ dưới đây sử dụng C/C++ để tính căn bậc 3 của 8 theo cách trên. Các bạn lưu ý em kiểu cho số mũ nhé. Vì 2 số nguyên chia cho nhau sẽ chỉ ra giá trị nguyên(1/3 = 0).

 
#include <stdio.h>
#include <math.h>
 
int main ()
{
    int a = 8;
    printf("%f", pow(a, 1.0/3));
}

Kết quả: 2.000000

Như vậy, để tính căn bậc n của số a, bạn sẽ dùng hàm pow để tính kết quả của phép tính a1/n

Tính căn bậc 3 không dùng hàm pow

Để tính căn bậc 3 của 1 số dương a ( tổnq quát căn bậc n ), ta phải xây dựng một chuỗi ( toán học ) mà chuỗi này hội tụ về căn bậc 3 của a. Sau đó lặp hữu hạn 1 số lần để được kết quả với độ chính xác nào đó chấp nhận được. Sau day la chuong trinh trong C de thuật tính căn bậc 3 cua 2 voi độ chính xác 0.00000001 chỉ bằng các phép toán +, – , *, / mà thôi.

Dưới đây là lời giải tính căn bậc 3 của 2 của một thành viên trong Cộng đồng C Việt:

 
////////////////////////////////////////////////////////////////////////
// Cube root of 2 by Nguyen Van Noi - DHTG
// Email : nvnoi76@yahoo.com
///////////////////////////////////////////////////////////////////////
 
#include <stdio.h>
double myabs(double x)
{
    return ((x>=0)?x:(-x));
}
void main()
{
    double a=2.0, xo, xn=1, e=1e-8;
    do
    {
       xo=xn;
       xn=(a/xo/xo+2.0*xo)/3.0;
    }
    while (myabs(xn-xo)>e);
    printf("Can bac 3 cua 2 = %1.8f\n",xn);
}

Một lời giải khác do mình tổng hợp:

Ý tưởng: Tìm phần nguyên trước, sau đó tìm phần thập phân.

Với cách làm này, bạn chỉ thu được kết quả xấp xỉ với đáp án chính xác. Độ chính xác phụ thuộc vào giá trị precision mà bạn mong muốn.

 
#include <stdio.h>
 
double cubeRoot(double n) {
   double i, precision = 0.000001;
   
   for(i = 1; (i*i*i) <= n; ++i);   //tim phan nguyen
 
   for(--i; (i*i*i) < n; i += precision);  //tim phan thap phan
   
   return i;
}
 
int main() {
   int n = 125;
 
   printf("Can bac ba cua %d = %lf", n, cubeRoot(n));
 
   return 0;
}

Chạy thử:

 
Can bac 3 cua 125 = 5.000000
Căn Bậc N Trong C
Căn Bậc N Trong C

V. Dấu Căn Bậc 2 Trong C++

Hàm sqrt() trong C

Hàm double sqrt(double x) trong Thư viện C trả về căn bậc hai của x.

Khai báo hàm sqrt() trong C

Dưới đây là phần khai báo cho hàm sqrt() trong C:

double sqrt(double x)

Tham số

x − Đây là giá trị số thực dấu chấm động.

Trả về giá trị

Hàm này trả về căn bậc hai của x.

Ví dụ

Chương trình C sau minh họa cách sử dụng của hàm sqrt() trong C:

#include <stdio.h>
#include <math.h>

int main ()
{

   printf("Can bac hai cua %lf bang %lf\n", 4.0, sqrt(4.0) );
   printf("Can bac hai cua %lf bang %lf\n", 5.0, sqrt(5.0) );
   
   return(0);
}

Biên dịch và chạy chương trình C trên sẽ cho kết quả:

Dấu Căn Bậc 2 Trong C++
Dấu Căn Bậc 2 Trong C++

VI. Hàm Căn Bậc 3 Trong C++

Bài tập C: Tìm căn bậc ba của một số

Tương tự như cách tìm căn bậc hai (không sử dụng hàm sqrt()), bài tập C này có thể được giải theo hai bước:

Tìm phân nguyên

Tìm phần thập phân

Đây chỉ là cách giải cho kết quả xấp xỉ, và độ chính xác phụ thuộc vào số các số sau dấu thập phân.

Bạn theo dõi phần code dưới đây để hiểu cách tìm căn bậc ba của một số bất kỳ trong C.

Chương trình C

Dưới đây là chương trình C để giải bài tập tìm căn bậc ba của một số trong C:

#include <stdio.h>

double cubeRoot(double n) {
   double i, precision = 0.000001;
   
   for(i = 1; (i*i*i) <= n; ++i);   //tim phan nguyen

   for(--i; (i*i*i) < n; i += precision);  //tim phan thap phan
   
   return i;
}

int main() {
   int n = 125;

   printf("Can bac ba cua %d = %lf", n, cubeRoot(n));

   return 0;
}

Biên dịch chương trình C trên sẽ cho kết quả:

Hàm Căn Bậc 3 Trong C++
Hàm Căn Bậc 3 Trong C++

VII. Bài Tập Về Căn Bậc 2 Trong C++

Đề bài

Viết chương trình giải phương trình bậc 2 trong C++. Phương trình bậc 2 có dạng:

 Bài Tập Về Căn Bậc 2 Trong C++
Bài Tập Về Căn Bậc 2 Trong C++

Lời giải

Lời giải của chúng tôi sử dụng phương thức sqrt() để tính căn bậc 2 của một số trong C++.

#include <iostream>
#include <cmath>
  
using namespace std;
  
/**
 * giai phuong trinh bac 2: ax2 + bx + c = 0
 * 
 * @param a: he so bac 2
 * @param b: he so bac 1
 * @param c: so hang tu do
 */
void giaiPTBac2(float a, float b, float c) {
    // kiem tra cac he so
    if (a == 0) {
        if (b == 0) {
            printf("Phuong trinh vo nghiem!");
        } else {
            printf("Phuong trinh co mot nghiem: x = %f", (-c / b));
        }
        return;
    }
    // tinh delta
    float delta = b*b - 4*a*c;
    float x1;
    float x2;
    // tinh nghiem
    if (delta > 0) {
        x1 = (float) ((-b + sqrt(delta)) / (2*a));
        x2 = (float) ((-b - sqrt(delta)) / (2*a));
        printf("Phuong trinh co 2 nghiem la: x1 = %f va x2 = %f", x1, x2);
    } else if (delta == 0) {
        x1 = (-b / (2 * a));
        printf("Phong trinh co nghiem kep: x1 = x2 = %f", x1);
    } else {
        printf("Phuong trinh vo nghiem!");
    }
}
 
/**
 * ham main
 */
int main() {
 float a, b, c;
    cout << "Nhap he so bac 2, a = ";
    cin >> a;
    cout << "Nhap he so bac 1, b = ";
    cin >> b;
    cout << "Nhap so hang tu do, c = ";
    cin >> c;
    giaiPTBac2(a, b, c);
    return 1;
}

Kết quả:

Nhap he so bac 2, a = 2
Nhap he so bac 1, b = 3
Nhap so hang tu do, c = 1
Phuong trinh co 2 nghiem la: x1 = -0.500000 va x2 = -1.000000

VIII. Tìm Căn Bậc Hai Không Dùng Sqrt

Tìm căn bậc hai không sử dụng hàm sqrt? Bạn nghĩ sao? Hôm nay Techacademy quay lại cùng với một bài toán đơn giản mà phức tạp. Chắc hẳn khi học lập trình bạn nào cũng đã từng tính căn bậc hai của một số. Chắc không ai quên được tên hàm nó chính là hàm sqrt(). Nhưng các bạn đã bao giờ tự hỏi làm sao để có thể tự viết hàm sqrt() này chưa? Hãy cũng mình đi tìm giải pháp trong bài viết này nhé.

Tìm Căn Bậc Hai Không Dùng Sqrt
Tìm Căn Bậc Hai Không Dùng Sqrt

Tìm căn bậc hai của một số không dùng hàm thì sao?

Ý tưởng tìm căn bậc hai:

  • Khai báo 1 epsilon đặt sai số chấp nhận, vì căn bậc hai của một số có thể là số thập phân vô hạn.
  • Khởi tạo kết quả bằng 1.0

Nếu kết quả có sai số cao hơn epsilon, cập nhật lại kết quả theo công thức

 
result = (number/result – result) / 2 + result;
  • Mình sẽ thu hẹp dần giới hạn trên và giới hạn dưới của kết quả, lấy trung bình hiệu khoảng cách giới hạn đó để cập nhật kết quả. Điều này luôn đảm bảo rằng giới hạn trên dưới sẽ bị thu hẹp nhưng sẽ luôn bao bọc đáp án.
  • Nếu kết quả có sai số nhỏ hơn EPSILON thì dừng lại và lấy kết quả đó làm đáp án.

Ví dụ: Bạn cần tính căn bậc 2 của 5.

  • Ta khởi tạo kết quả là 1.0. Kết quả này dĩ nhiên không đúng rồi, nên đáp số sẽ nằm trong khoảng 1.0 và 5/1.0 = 5.0.
  • Lấy một nửa hiệu khoảng 1.0 đến 5.0 là 1.0 + (5.0 – 1.0)/2 được 3.0. Nhưng 3.0 lớn hơn kết quả thực(bình phương là biết, trong code thì sai số để check),
  • Lại lấy nửa hiệu khoảng từ 5/3.0 đến 3.0 cộng vào kết quả hiện tại(3.0) = 3.0 + (5/3.0 – 3.0) = 2.33…
  • Cứ làm tiếp tục như vậy cho tới khi sai số nhỏ hơn EPSILON

Code đầy đủ cho ý tưởng này là

 
// Code from https://nguyenvanhieu.vn
 
#include <stdio.h>
#include <math.h>
 
#define EPSILON 0.0001f
 
double mySqrt(int number)
{
    double result = 1.0f;
    while (fabs(result * result – number) / number >= EPSILON)
        result = (number / result  – result) / 2 + result;
    return result;
}
 
int main()
{
    int x;
    printf(“Input x: “);
    scanf(“%d”, &x);
    printf(“Sqrt of %d = %1.9fn”, x, mySqrt(x));
    return 0;
}

Và chạy thử xem sao

 
Input x: 5
Sqrt of 5 = 2.236069
 

The post Căn Bậc 2 Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/can-bac-2-trong-c/

Con Trỏ Trong C++

Con trỏ trong lập trình là 1 định nghĩa hơi khó nhằn đối với các bạn mới học về C++. Không ngoa khi nói rằng C++ khó vì có con trỏ. Tuy nhiên ví như làm chủ được con trỏ, bạn có thể hiểu và thao tác với dữ liệu trong bộ nhớ máy tính, và những kiến ​​thức liên quan mà bạn học được thông qua con trỏ cũng rất hữu ích cho việc học các ngôn ngữ hướng đối tượng sau này như Java chẳng hạn.

Hãy cùng tìm hiểu con trỏ trong C++ là gì, quan hệ giữa con trỏ và địa chỉ trong C++, cấu trúc, vai trò và cách sử dụng con trỏ sau bài học này nhé.

I. Khai Báo Con Trỏ Trong C++

Để khai báo con trỏ trong C++, chung ta sử dụng với cấu trúc ngữ pháp sau đây:

type *p;

Trong đó type là kiểu dữ liệu của con trỏ, và p là tên con trỏ. Lưu ý là kiểu dữ liệu của con trỏ phải giống với kiểu dữ liệu của dữ liệu cần lưu địa chỉ trong con trỏ.

Ví dụ, chúng ta khai báo con trỏ tên p với kiểu int như sau:

int *p;

Lưu ý là các cách viết sau đây cũng được chấp nhận khi khai báo con trỏ trong C++:

int* p;
int * p;
Khai BáoKhai Báo Con Trỏ Trong C++Con Trỏ Trong C++
Khai Báo Con Trỏ Trong C++

II. Cấp Phát Bộ Nhớ Cho Con Trỏ Trong C++

Ngôn ngữ C++ hỗ trợ ba loại cấp phát bộ nhớ cơ bản, hai loại trong số đó bạn đã được học ở các bài học trước:

1. Cấp phát bộ nhớ tĩnh (Static memory allocation):

  • Xảy ra trên các biến tĩnh và biến toàn cục.
  • Vùng nhớ của các loại biến này được cấp phát một lần khi chương trình bắt đầu chạy và vẫn tồn tại trong suốt thời gian tồn tại của chương trình.
  • Kích thước của biến/mảng nên được biết tại thời điểm biên dịch chương trình.

2. Cấp phát bộ nhớ tự động (Automatic memory allocation):

  • Xảy ra trên các tham số hàm và biến cục bộ.
  • Vùng nhớ của các loại biến này được cấp phát khi chương trình đi vào khối lệnh và được giải phóng khi khối lệnh bị thoát.
  • Kích thước của biến/mảng phải được biết tại thời điểm biên dịch chương trình.

3. Cấp phát bộ nhớ động (Dynamic memory allocation) sẽ được kể tới trong bài học này.

Trong tất cả những trường hợp, cấp phát bộ nhớ tĩnh và tự động có thể đáp ứng tốt các đề nghị của chương trình. Tuy nhiên, ta cùng xem ví dụ bên dưới:

Ví dụ: Chúng ta cần sử dụng một chuỗi để lưu tên của người dùng, nhưng chúng ta không biết tên của họ dài bao nhiêu cho đến khi họ nhập tên. Hoặc chúng ta cần lưu trữ danh sách nhân viên trong một công ty, nhưng chúng ta không biết trước được công ty đó sẽ có bao nhiêu nhân viên.

Đối với cấp phát bộ nhớ tĩnh và tự động, kích thước của biến/mảng phải được biết tại thời điểm biên dịch chương trình. Vì vậy, điều tốt nhất chúng ta có thể làm là cố gắng đoán một kích thước tối đa của các biến đó:

char name[30]; // tên không quá 30 ký tự
Staff staff[500]; // công ty không quá 500 nhân viên

Khuyết điểm của cách khai báo trên:

+ Gây lãng phí bộ nhớ nếu các biến không thực sự sử dụng hết kích thước khi khai báo. Ví dụ: nếu công ty chỉ có 100 nhân viên, chúng ta có 400 vùng nhớ nhân viên không được sử dụng tới.

+ Thứ hai, hầu hết các biến thông thường (bao gồm mảng tĩnh) được cấp phát trong một phần bộ nhớ gọi là ngăn xếp (stack). Kích thước bộ nhớ stack cho một chương trình khá nhỏ (khoảng 1Mb với Visual Studio), nếu yêu cầu cấp phát vùng nhớ vượt quá con số này, chương trình sẽ bị đóng bởi hệ điều hành với lỗi stack overflow.

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow 

+ Thứ ba, điều gì xảy ra nếu công ty có 600 nhân viên, trong khi mảng staff chỉ có 500 phần tử. Lúc này, chương trình sẽ bị giới hạn bởi kích thước được khai báo ban đầu.

Để giải quyết những hạn chế trên, cấp phát bộ nhớ động được ra đời.

Cấp Phát Bộ Nhớ Cho Con Trỏ Trong C++
Cấp Phát Bộ Nhớ Cho Con Trỏ Trong C++

III. Con Trỏ Trong C++ Dùng Để Làm Gì

Tác dụng của con trỏ trong c chính là để lưu giữ địa chỉ của dữ liệu trong bộ nhớ máy tính, và bằng cách truy cập vào địa chỉ này, chúng ta có thể lấy được giá trị của dữ liệu tại đó.

Ngoài ra thì giá trị của con trỏ cũng là một số, nên chúng ta cũng có thể thực hiện các phép tính toán với con trỏ, ví dụ như cộng thêm hoặc hoặc trừ đi một số lượng đơn vị.

Do đó, con trỏ trong C sẽ được dùng để làm 1 trong 2 công việc sau đây trong chương trình:

  1. Thao tác với địa chỉ bằng các phép tính toán với số được lưu trong nó
  2. Thao tác với giá trị tại địa chỉ mà nó lưu mà thôi.
Con Trỏ Trong C++ Dùng Để Làm Gì
Con Trỏ Trong C++ Dùng Để Làm Gì

IV. Mảng Con Trỏ Trong C++

Trước khi chúng ta hiểu về khái niệm mảng các con trỏ, chúng ta xem xét ví dụ sau, mà sử dụng một mảng gồm 3 số integer:

#include <iostream>
 
using namespace std;
const int MAX = 3;
 
int main ()
{
   int  var[MAX] = {10, 100, 200};
 
   for (int i = 0; i < MAX; i++)
   {
      cout << "Gia tri cua var[" << i << "] = ";
      cout << var[i] << endl;
   }
   return 0;
}

Khi code trên được biên dịch và thực thi, nó cho kết quả sau:

Gia tri cua var[0] = 10
Gia tri cua var[1] = 100
Gia tri cua var[2] = 200

Có một tình huống khi chúng ta muốn duy trì một mảng, mà có thể lưu giữ các con trỏ tới một kiểu dữ liệu int hoặc char hoặc bất kỳ kiểu nào khác. Sau đây là khai báo một mảng của các con trỏ tới một integer:

int *contro[MAX];

Nó khai báo contro như là một mảng các con trỏ MAX kiểu integer. Vì thế, mỗi phần tử trong contro, bây giờ giữ một con trỏ tới một giá trị int. Ví dụ sau sử dụng 3 số integer, mà sẽ được lưu giữ trong một mảng các con trỏ như sau:

#include <iostream>
 
using namespace std;
const int MAX = 3;
 
int main ()
{
   int  var[MAX] = {10, 100, 200};
   int *contro[MAX];
 
   for (int i = 0; i < MAX; i++)
   {
      contro[i] = &var[i]; // gan dia chi cua so nguyen.
   }
   for (int i = 0; i < MAX; i++)
   {
      cout << "Gia tri cua var[" << i << "] = ";
      cout << *contro[i] << endl;
   }
   return 0;
}

Khi code trên được biên dịch và thực thi, nó cho kết quả sau:

Gia tri cua var[0] = 10
Gia tri cua var[1] = 100
Gia tri cua var[2] = 200

Bạn có thể sử dụng một mảng các con trỏ tới ký tự để lưu giữ một danh sách các chuỗi như sau:

#include <iostream>
 
using namespace std;
const int MAX = 4;
 
int main ()
{
   char *tensv[MAX] = {
                   "Nguyen Thanh Tung",
                   "Tran Minh Chinh",
                   "Ho Ngoc Ha",
                   "Hoang Minh Hang",
   };

   for (int i = 0; i < MAX; i++)
   {
      cout << "Gia tri cua tensv[" << i << "] = ";
      cout << tensv[i] << endl;
   }
   return 0;
}

Chạy chương trình C++ trên sẽ cho kết quả như hình sau:

Mảng Con Trỏ Trong C++
Mảng Con Trỏ Trong C++

V. Con Trỏ Hàm Trong C++

Con trỏ hàm là một biến lưu trữ địa chỉ của một hàm, thông qua biến đó, ta có thể gọi hàm mà nó trỏ tới.

Cú pháp khai báo con trỏ hàm:

<kiểu trả về> (*<tên con trỏ>)(<danh sách tham số>);

int(*fcnPtr)(int); // con trỏ hàm nhận vào 1 biến kiểu int và trả về kiểu int
void(*fcnPtr)(int, int); // con trỏ hàm nhận vào 2 biến kiểu int và trả về kiểu void

Chú ý: Dấu ngoặc () quanh *fcnPtr là bắt buộc.

VI. Con Trỏ Trong Class C++

Một con trỏ tới một lớp trong C++ được thực hiện theo cách giống hệt như một con trỏ tới một cấu trúc; và để truy cập các thành viên của một con trỏ tới một lớp bạn sử dụng toán tử truy cập thành viên trong C++ là toán tử ->, như khi bạn thực hiện với các con trỏ tới cấu trúc. Cũng như với tất cả con trỏ, bạn phải khai báo con trỏ trước khi sử dụng nó.

Bạn thử ví dụ sau để hiểu khái niệm con trỏ tới một lớp trong C++:

#include <iostream>
 
using namespace std;

class Box
{
   public:
      // phan dinh nghia Constructor
      Box(double dai=1.0, double rong=1.0, double cao=1.0)
      {
         cout <<"Constructor duoc goi." << endl;
         chieudai = dai;
         chieurong = rong;
         chieucao = cao;
      }
      double theTich()
      {
         return chieudai * chieurong * chieucao;
      }
   private:
      double chieudai;     // chieu dai cua mot box
      double chieurong;    // chieu rong cua mot box
      double chieucao;     // chieu cao cua mot box
};

int main(void)
{
   Box Box1(2.4, 4.2, 2.2);    // khai bao box1
   Box Box2(4.5, 2.0, 3.2);    // khai bao box2
   Box *controBox;                // khai bao con tro toi mot class.

   // luu giu dia chi cua doi tuong dau tien
   controBox = &Box1;

   // bay gio thu truy cap mot thanh vien boi su dung toan tu truy cap thanh vien
   cout << "The tich cua Box1 la: " << controBox->theTich() << endl;

   // luu giu dia chi cua doi tuong thu hai
   controBox = &Box2;

   // bay gio thu truy cap mot thanh vien boi su dung toan tu truy cap thanh vien
   cout << "The tich cua Box2 la: " << controBox->theTich() << endl;
  
   return 0;
}

Biên dịch và chạy chương trình C++ trên sẽ cho kết quả sau:

Con Trỏ Trong Class C++
Con Trỏ Trong Class C++

VII. Con Trỏ Trong Struct C++

Chào các bạn đang theo dõi khóa học lập trình trực tuyến ngôn ngữ C++.
Chúng ta cùng tiếp tục tìm hiểu về kiểu dữ liệu tự định nghĩa thông qua từ khóa struct mà ngôn ngữ C++ hỗ trợ. Trong bài học này, mình sẽ trình bày về kiểu struct khi sử dụng kết hợp với con trỏ.

Như các bạn đã học trong bài trước, sau khi chúng ta tự định nghĩa một struct, compiler sẽ coi tên gọi của struct đó như là một kiểu dữ liệu. Điều này có nghĩa khi chúng ta sử dụng các kiểu dữ liệu built-in để tạo ra các biến, tham chiếu hoặc con trỏ thì chúng ta cũng có thể sử dụng kiểu struct để tạo ra biến struct, tham chiếu struct và con trỏ kiểu struct (Pointer to struct).

Pointer to struct

Đầu tiên, chúng ta cùng định nghĩa một kiểu dữ liệu theo ý muốn. Dưới đây, mình định nghĩa một kiểu dữ liệu có tên là Letter:

struct Letter
{
   
};

Trong struct Letter mình chưa định nghĩa các trường dữ liệu, lúc này Letter là một kiểu dữ liệu rỗng. Nhưng ngôn ngữ C++ vẫn đặt kích thước của kiểu Letter này là 1 bytes.

Mục đích là để đảm bảo địa chỉ của 2 biến được tạo ra sẽ có địa chỉ khác nhau. Tuy nhiên, định nghĩa ra một struct rỗng không có tác dụng gì trong chương trình, chúng ta cùng thêm vào một số trường dữ liệu cho Letter:

struct Letter
{
   char from[50];
   char to[50];
};

Một lá thư sẽ có thông tin về người gửi và người nhận, nên mình thêm vào 2 trường dữ liệu kiểu C-style string dùng để lưu thông tin mà người dùng điền vào một lá thư.

Mình vừa định nghĩa xong một kiểu dữ liệu mới để phục vụ cho chương trình của mình. Bây giờ chúng ta cùng tạo ra một đơn vị từ kiểu dữ liệu trên (mình thao tác luôn trong hàm main):

int main()
{
   Letter myLetter;
   
   return 0;
}

Với mỗi biến kiểu Letter được tạo ra, chương trình sẽ yêu cầu cấp phát 100 bytes (50 bytes cho trường dữ liệu from và 50 bytes cho trường dữ liệu to), và chắc chắn rồi, biến đó sẽ có một địa chỉ xác định được thông qua toán tử address-of.

int main()
{
   Letter myLetter;
   std::cout << "Address of myLetter: " << &myLetter << std::endl;
   std::cout << "Address of from field: " << &myLetter.from << std::endl;
   
   return 0;
}

Ở đoạn chương trình trên, mình in ra địa chỉ của biến myLetter, đồng thời in ra luôn địa chỉ của trường dữ liệu from của biến myLetter. Kết quả cho thấy 2 địa chỉ được in ra có giá trị hoàn toàn giống nhau. Điều này có nghĩa địa chỉ của trường dữ liệu đầu tiên trong một biến struct cũng là địa chỉ của biến struct đó.

Các bạn có thể liên hệ struct với mảng một chiều trong C/C++, khi mảng một chiều mà tập hợp các phần tử có cùng kiểu dữ liệu được bao bọc bởi tên mảng một chiều và địa chỉ của mảng một chiều cũng là địa chỉ của phần tử đầu tiên trong mảng, một biến struct sẽ bao gồm tập hợp các trường dữ liệu mà địa chỉ của biến struct sẽ là địa chỉ của trường dữ liệu được khai báo đầu tiên trong struct.

Và như các bạn cũng đã học về con trỏ (Pointer), kiểu dữ liệu của con trỏ dùng để xác định kiểu dữ liệu của vùng nhớ mà con trỏ có thể trỏ đến. Vậy thì để cho con trỏ trỏ đến một địa chỉ của biến kiểu struct, chúng ta cần có một con trỏ cùng kiểu struct với biến được trỏ đến.

Letter myLetter;
Letter *pLetter = &myLetter;

Dù kích thước của kiểu dữ liệu struct có lớn bao nhiêu, biến con trỏ cũng chỉ có kích thước 4 bytes trên hệ điều hành 32 bits và kích thước 8 bytes trên hệ điều hành 64 bits (đủ để trỏ đến toàn bộ địa chỉ trên bộ nhớ ảo).

Access struct members

Trong bài học trước, các bạn đã biết cách truy cập đến các trường dữ liệu của các biến struct thông qua member selection operator (dấu chấm). Nhưng khi sử dụng Pointer to struct, member selection operator được sử dụng dưới cách viết khác. Để phân biệt sự khác nhau khi sử dụng member selection operator cho biến struct thông thường và một Pointer to struct, các bạn cùng xem ví dụ bên dưới:

struct BankAccount
{
   __int64 accountNumber;
   __int64 balance;
};

int main()
{
   BankAccount myAccount = { 123456789, 50 }; // $50
   BankAccount *pAccount = &myAccount; 

   std::cout << "My bank account number: " << myAccount.accountNumber << std::endl;
   std::cout << "My bank account number: " << pAccount->accountNumber << std::endl;

   std::cout << "My balance: " << myAccount.balance << std::endl;
   std::cout << "My balance: " << pAccount->balance << std::endl;

   return 0;
}

Như các bạn thấy, kết quả của việc truy xuất giá trị thông qua tên biến struct và con trỏ kiểu struct là hoàn toàn giống nhau, và chúng đều dùng toán tử member selection. Tuy nhiên, để phân biệt biến con trỏ và biến thông thường, biến con trỏ kiểu struct sẽ truy cập đến các trường dữ liệu trong vùng nhớ bằng toán tử (->). Hai toán tử này cùng tên, chỉ khác nhau về cách biểu diễn.

Một số nhầm lần khi sử dụng struct và Pointer to struct

Khi mới tìm hiểu về Pointer to struct, các bạn có thể bị nhầm lẫn giữa cách khởi tạo hoặc gán giá trị cho biến struct thông thường và biến con trỏ struct.

struct BankAccount
{
   __int64 accountNumber;
   __int64 balance;
};

int main()
{
   BankAccount myAccount = { 12345, 50 };

   BankAccount *pAccount = { 12345, 50 }; //error

   return 0;
}

Đoạn chương trình trên báo lỗi vì biến con trỏ chỉ nhận giá trị là địa chỉ. Tuy nhiên, lỗi này có thể thấy dễ dàng vì Visual studio đưa ra thông báo lỗi ngay. Dưới đây là cách gán giá trị đúng khi mình sử dụng toán tử dereference cho biến con trỏ struct để thay đổi giá trị bên trong vùng nhớ:

struct BankAccount
{
   __int64 accountNumber;
   __int64 balance;
};

int main()
{
   BankAccount myAccount = { 0, 0 };

   BankAccount *pAccount = &myAccount;

   *pAccount = { 12345, 50 };

   std::cout << pAccount->accountNumber << " " << pAccount->balance << std::endl;

   return 0;
}

Hoặc một cách khác là chúng ta cấp phát vùng nhớ cho biến con trỏ struct, và dereference đến đó để gán giá trị cho nó:

BankAccount *pAccount = new BankAccount;
*pAccount = { 12345, 50 };

Và các bạn lưu ý khi sử dụng biến kiểu con trỏ struct thì chúng ta sử dụng toán tử member selection này (->). Có một số bạn nhầm lẫn giữa biến con trỏ struct và trường dữ liệu kiểu con trỏ. Ví dụ:

struct BankAccount
{
   char *name;
   __int64 accountNumber;
   __int64 balance;
};

Mình thêm vào struct một trường dữ liệu kiểu con trỏ char nhưng việc truy xuất đến trường dữ liệu này không có gì thay đổi khi mình sử dụng biến struct thông thường.

BankAccount myAccount = { "Le Tran Dat", 12345, 50 };

std::cout << myAccount.name << std::endl;
std::cout << myAccount.accountNumber << std::endl;
std::cout << myAccount.balance << std::endl;

Sẽ phức tạp hơn một chút khi các bạn sử dụng các nested struct. Ví dụ:

struct BankAccount
{
   Date registrationDate;
   __int64 accountNumber;
   __int64 balance;
};

int main()
{
   BankAccount *pAccount = new BankAccount;
   *pAccount = { {2, 5, 2016}, 12345, 50 };

   std::cout << pAccount->registrationDate.year << std::endl;

   return 0;
}

Như các bạn thấy, từ biến con trỏ pAccount truy xuất vào các trường dữ liệu bên trong thì mình dùng toán tử (->), nhưng trường dữ liệu Date trong struct BankAccount là biến thông thường, nên mình dùng dấu chấm để truy xuất dữ liệu ngày đăng kí.

Con Trỏ Trong Struct C++
Con Trỏ Trong Struct C++

VIII. Con Trỏ Null Trong C++

Con trỏ NULL trong C++ là một hằng với một giá trị là 0 được định nghĩa trong một vài thư viện chuẩn, gồm iostream.

#include <iostream>
 
using namespace std;
 
int main () {
    int  *ptr = NULL;
    cout << "Gia tri cua contro la " << ptr ;
    return 0;
}

Kết quả:

Gia tri cua contro la 0

Trên hầu hết các hệ điều hành, các chương trình không được phép truy cập bộ nhớ tại địa chỉ 0, vì bộ nhớ đó được dự trữ bởi hệ điều hành. Tuy nhiên, địa chỉ bộ nhớ 0 có ý nghĩa đặc biệt, nó chỉ ra rằng con trỏ không được trỏ tới một vị trí ô nhớ có thể truy cập. Nhưng theo qui ước, nếu một con trỏ chứa giá trị 0, nó được xem như là không trỏ tới bất cứ thứ gì.

Để kiểm tra một con trỏ null trong C++, bạn có thể sử dụng lệnh if như sau:

if(contro)     // true neu contro khong la NULL
if(!contro)    // true neu contro la NULL
Con Trỏ Null Trong C++
Con Trỏ Null Trong C++

IX. Nhập Xuất Mảng Bằng Con Trỏ Trong C++

Bằng cách sử dụng con trỏ mảng, chúng ta có thể chỉ định vị trí các phần tử trong mảng, cũng như là truy cập và lấy giá trị của các phần tử đó.

Ứng dụng điều này, chúng ta cũng có thể nhập xuất mảng bằng con trỏ trong C++ như sau.

Nhập mảng bằng con trỏ trong C++

Trong bài Nhập xuất mảng trong C++ chúng ta đã biết cách tạo hàm nhập trực tiếp các giá trị từ bàn phím vào mảng như sau:

/*Tạo hàm nhập mảng 1 chiều trong C++*/
void input_array(int array[], int length){
    //array: tên mảng
    //length: độ dài mảng
    for (short i = 0; i < length; i++) cin >> array[i];
}

Trong đó array và length lần lượt là tên và độ dài (số phần tử) của mảng cần nhập.

Để nhập mảng bằng con trỏ trong C++ chúng ta chỉ cần sử dụng giá trị con trỏ thay cho mảng, và thay vì dùng index để chỉ định vị trí nhập dữ liệu, thì chúng ta sẽ sử dụng trực tiếp giá trị con trỏ để chỉ định vị trí cần nhập. Chúng ta viết hàm nhập mảng bằng con trỏ trong C++ như sau:

/*Tạo hàm nhập mảng bằng con trỏ trong C++*/
void input_array(int array[], int length){
    //array: tên mảng
    //length: độ dài mảng
    for (short i = 0; i < length; i++) cin >> *(array + i);
}

Lưu ý tại đây chúng ta sử dụng lệnh cin để nhập dữ liệu vào mảng, nên cần chỉ định bản thân dữ liệu mà con trỏ chỉ đến bằng cách thêm dấu hoa thị * trước con trỏ.

Tuy nhiên nếu sử dụng hàm scanf được kế thừa từ ngôn ngữ C để nhập mảng bằng con trỏ trong C++, thay vì chỉ định dữ liệu thì chúng ta cần chỉ định địa chỉ của dữ liệu đã được lưu vào con trỏ như sau:

/*Tạo hàm nhập mảng bằng con trỏ trong C*/
void input_array(int *array, int length){
    //array: tên mảng
    //length: độ dài mảng
    for (short i = 0; i < length; i++) scanf("%d", (array + i));
}

Xuất mảng bằng con trỏ trong C++

Trong bài xuất xuất mảng trong C++ chúng ta đã biết cách tạo hàm xuất trực tiếp các giá trị của mảng như sau:

/*Tạo hàm xuất mảng 1 chiều trong C++*/
void show_array(int array[], int length){
    //array: tên mảng
    //length: độ dài mảng
    for(short i = 0; i < length; i++)  cout << array[i] <<' ';   
    cout << endl;
}

Trong đó array và length lần lượt là tên và độ dài (số phần tử) của mảng cần xuất.

Để xuất mảng bằng con trỏ trong C++ chúng ta chỉ cần sử dụng con trỏ thay cho mảng, và thay vì truy xuất giá trị các phần tử của mảng bằng index thì chúng ta sẽ dùng tên con trỏ và dấu hoa thị để xuất giá trị đó. Chúng ta viết hàm xuất mảng bằng con trỏ trong C++ như sau:

/*Tạo hàm xuất mảng bằng con trỏ trong C++*/
void show_array(int array[], int length){
    //array: tên mảng
    //length: độ dài mảng
    for(short i = 0; i < length; i++)  cout << *(array + i) <<' ';   
    cout << endl;
}

Chương trình mẫu nhập xuất mảng bằng con trỏ trong C++

Dưới đây là chương trình mẫu sử dụng các hàm trên để nhập xuất mảng bằng con trỏ trong C++:

#include <iostream>
using namespace std;

/*Tạo hàm nhập mảng bằng con trỏ trong C++*/
void input_array(int array[], int length){
    //array: tên mảng
    //length: độ dài mảng
    for (short i = 0; i < length; i++) cin >> *(array + i);
}

/*Tạo hàm xuất mảng bằng con trỏ trong C++*/
void show_array(int array[], int length){
    //array: tên mảng
    //length: độ dài mảng
    for(short i = 0; i < length; i++)  cout << *(array + i) <<' ';   
    cout << endl;
}

int main(){
    /*Nhập mảng bằng con trỏ trong C++*/
    int n;
    cout << ">>Nhap so phan tu: ";
    cin >>  n;
 
    int array[n], *p;
    cout << (">>Nhap phan tu:\n");
    input_array(array, n);
    
    /*Xuất mảng bằng con trỏ trong C++*/ 
    cout << (">>Mang vua nhap:\n"); 
    show_array(array, n);
}

Kết quả:

>>Nhap so phan tu: 5
>>Nhap phan tu:
1 2 3 4 5
>>Mang vua nhap:
1 2 3 4 5
Nhập Xuất Mảng Bằng Con Trỏ Trong C++
Nhập Xuất Mảng Bằng Con Trỏ Trong C++

X. Con Trỏ Và Tham Chiếu Trong C++

C và C++ hỗ trợ con trỏ cái mà khác với hầu hết các ngôn ngữ lập trình khác. Các ngôn ngữ khác bao gồm C ++, Java, Python, Ruby, Perl và PHP đều hỗ trợ tham chiếu.

Nhìn bề ngoài, cả hai tham chiếu và con trỏ đều rất giống nhau, cả hai đều được sử dụng để có một biến cung cấp quyền truy cập cho một biến khác. Với việc cả hai đều cung cấp nhiều khả năng giống nhau, người ta thường không rõ điều gì khác biệt giữa các cơ chế khác nhau này. Trong bài viết này, tôi sẽ cố gắng minh họa sự khác biệt giữa con trỏ và tham chiếu.

Con trỏ: Con trỏ là một biến chứa địa chỉ bộ nhớ của một biến khác. Một con trỏ cần được deference với toán tử * để truy cập vào vị trí bộ nhớ mà nó trỏ tới.

Tham chiếu: Một biến tham chiếu là một bí danh, tức là một tên khác của một biến đã tồn tại. Một tham chiếu, giống như một con trỏ, cũng được hiện thựcbằng cách lưu trữ địa chỉ của một đối tượng.
Một tham chiếu có thể được coi là một con trỏ hằng (đừng nhầm với một con trỏ đến một giá trị không đổi!) với tính năng tự động chuyển hướng, tức là trình biên dịch sẽ áp dụng toán tử * cho bạn.

int i = 3; 
 
// Một con trỏ đến biến i
// (lưu trữ địa chỉ của i)
int *ptr = &i; 
 
// Một tham chiếu (bí danh) của i.
int &ref = i;

Sự khác nhau giữa Con trỏ và Tham chiếu

Khởi tạo

Một con trỏ có thế được khai báo và khởi tạo đồng thời hoặc trên nhiều dòng.

C++
int a = 10;        
int *p = &a;    
hay
int *p;
p = &a;

Trong khi tham chiếu phải được khởi tạo cùng lúc với khai báo.

int a=10;
int &p=a;  //OK
nhưng
int &p;
p=a;      //NOK

Chú ý: Sự khác biệt này có thể thay đổi từ trình biên dịch này sang trình biên dịch khác. Sự khác biệt trên là đối với Turbo IDE.

Gán lại giá trị

Một con trỏ có thể được gán lại. Thuộc tính này hữu ích cho việc triển khai các cấu trúc dữ liệu như danh sách liên kết, cây, v.v. Xem ví dụ sau:

int a = 5;
int b = 6;
int *p;
p =  &a;
p = &b;

Trong khi đó, tham chiếu không gán lại mà phải được gán ngay lúc khởi tạo.

int a = 5;
int b = 6;
int &p = a;
int &p = b;   //Tại dòng này sẽ hiện lỗi là "không được khai báo nhiều lần"
 
//Tuy nhiên, lệnh như dưới đây thì ok.
int &q=p;

Địa chỉ bộ nhớ

Một con trỏ có địa chỉ bộ nhớ và kích thước riêng của nó trên ngăn xếp trong khi một tham chiếu chia sẻ cùng địa chỉ bộ nhớ (với biến gốc) nhưng cũng chiếm một số không gian trên ngăn xếp.

int &p = a;
cout << &p << endl << &a; // Địa chỉ của p và a là như nhau

Giá trị NULL

Con trỏ có thể được gán NULL trực tiếp, trong khi tham chiếu không thể. Các ràng buộc liên quan đến tham chiếu (không NULL, không gán lại) đảm bảo rằng các hoạt động cơ bản không rơi vào trường hợp ngoại lệ.

Con trỏ đến con trỏ

Bạn có thể có các con trỏ đến con trỏ cung cấp nhiều cấp độ chuyển hướng bổ sung. Trong khi đó, các tham chiếu chỉ cung cấp một mức chuyển hướng.

//Con trỏ,
int a = 10;
int *p;
int **q;  //Con trỏ đến con trỏ.
p = &a;
q = &p;
 
//Đối với tham chiếu,
 
int &p = a;
int &&q = p; //Lỗi, không có tham chiếu của tham chiếu.

Các phép tính toán học

Các phép toán số học khác nhau có thể được thực hiện trên con trỏ trong khi không có thứ gọi là Số học tham chiếu. (Nhưng bạn có thể lấy địa chỉ của một đối tượng được trỏ bởi một tham chiếu và thực hiện số học con trỏ trên đó như &obj + 5)

Khi nào sử dụng cái nào

Hiệu suất hoàn toàn giống nhau, vì các tham chiếu được triển khai bên trong dưới dạng con trỏ. Tuy nhiên, bạn vẫn có thể ghi nhớ một số điểm để quyết định khi nào sử dụng cái gì:

Sử dụng tham chiếu:

  • Trong các tham số hàm và kiểu trả về.

Sử dụng con trỏ:

  • Sử dụng con trỏ nếu cấn tính toán con trỏ số học hoặc NULL. Ví dụ đối với mảng (Lưu ý rằng truy cập mảng được thực hiện bằng cách sử dụng số học con trỏ).
  • Để triển khai các cấu trúc dữ liệu như danh sách liên kết, cây, v.v. và các thuật toán của chúng vì để trỏ ô khác nhau, chúng ta phải sử dụng khái niệm con trỏ.
Con Trỏ Và Tham Chiếu Trong C++
Con Trỏ Và Tham Chiếu Trong C++

XI. Ý Nghĩa Con Trỏ This Trong C++

Con trỏ this trong C++

This là một con trỏ đặc biệt dùng để trỏ đến địa chỉ của đối tượng hiện tại. Như vậy để truy cập đến các thuộc tính, phương thức của đối tượng hiện tại thì ta sẽ sử dụng con trỏ this. Hãy xem ví dụ dưới đây.

Ví dụ

#include <iostream>  
using namespace std;  
class NhanVien { 
    int msnv;    
    string ten;
    int tuoi;
    public:  
       void setData(int msnv, string ten, int tuoi) {  
            this->msnv = msnv;    
            this->ten = ten;    
            this->tuoi = tuoi; 
       }    
       void showData() {
            cout << "Ten nhan vien: " << this->ten << endl;
            cout << "Ma so nhan vien: " << this->msnv << endl;
            cout << "Tuoi: " << this->tuoi << endl;
       }    
};

Trong ví dụ này mình đã tạo ra ba thuộc tính để lưu trữ thông tin của một nhân viên đó là: manv, ten, tuoi. Ngoài ra mình có tạo thêm phương thức setData() dùng để gán dữ liệu cho sinh viên, và showData() dùng để hiển thị dữ liệu.

Trong phương thức setData() mình đã sử dụng từ khóa this->ten_thuoc_tinh để thực hiện phép gán dữ liệu cho các thuộc tính, còn ở phương thức showData() mình cũng sử dụng cú pháp tương tự để hiển thị dữ liệu của các thuộc tính. Như vậy công dụng của từ khóa this chính là một con trỏ và trỏ đến địa chỉ của đối tượng hiện tại.

Câu hỏi đặt ra là đối tượng hiện tại tại là gì? Để hiểu rõ hơn thì hãy xem đoạn code sử dụng class trên như sau:

Ví dụ

int main() { 
    // Nhan vien 1
    NhanVien n1 =  NhanVien();
    n1.setData(111231, "Nguyen Van A", 24);
    n1.showData();    
     
    // Nhan vien 2
    NhanVien n2 =  NhanVien();
    n2.setData(111232, "Nguyen Van B", 25);
    n2.showData();
    return 0;  
}

Trong ví dụ này mình đã tạo ra hai đối tượng sinh viên đó là n1 và n2, và con trỏ this của n1 sẽ trỏ đến chính đối tượng n1, con trỏ this của n2 sẽ trỏ đến chính đối tượng n2, đây ta gọi là đối tượng hiện tại.

Lưu ý: Trong các phương thức bình thường (không phải hàm khởi tạo) nếu bạn sử dụng tên của biến thì sẽ có hai trường hợp xảy ra.

  • Nếu biến đó không tôn tại trong phương thức mà nó lại trùng với tên thuộc tính thì mặc nhiên nó sẽ hiểu đó là thuộc tính.
  • Nếu biến đó có khai báo trong phương thức thì ta sẽ hiểu đó là biến bình thường, không phải là thuộc tính.

Một ví dụ khác về con trỏ this

Bạn hãy xem ví dụ dưới đây, đây là một ví dụ mình viết lại ở phần 1 và có một chút thay đổi.

Ví dụ

#include <iostream>  
using namespace std;  
class NhanVien { 
    int msnv;    
    string ten;
    int tuoi;
    public:  
       NhanVien(int msnv, string ten, int tuoi) {  
            cout << "Trong ham xay dung: " << endl;
            cout << "   msnv: " << msnv << endl;
            cout << "   ten: " << ten << endl;
            cout << "   Tuoi: " << tuoi << endl;
            msnv = msnv;    
            ten = ten;    
            tuoi = tuoi; 
       }    
       void HienThi() {
            cout << "Ham in thong tin cua doi tuong nhan vien: " << endl;
            cout << ten << endl;
            cout << "   Ma so nhan vien: " << msnv << endl;
            cout << "   Tuoi: " << tuoi << endl;
       }    
};  
   
int main() {  
    NhanVien n1 =  NhanVien(111231, "Nguyen Van A", 25);    
    n1.HienThi();    
    return 0;  
}
Ý Nghĩa Con Trỏ This Trong C++
Ý Nghĩa Con Trỏ This Trong C++

XII. Con Trỏ Void Trong C++

Để tìm hiều về con trỏ void trong C++, trước hết bạn cần phải nắm vững các kiến thức cơ bản về con trỏ void. Đừng lo lắng vì Techacademy đã chuẩn bị cho bạn trong các bài viết sau đây:

Con trỏ void trong C++ là gì

Trong số các kiểu con trỏ, bạn có thể xác định một con trỏ hơi khác thường được gọi là con trỏ void.

Giống như các loại con trỏ khác trong C++ thì con trỏ void cũng được sử dụng để lưu trữ địa chỉ của một dữ liệu trong bộ nhớ máy tính.

Tuy nhiên điều đặc biệt ở đây là, với kiểu dữ liệu mà con trỏ void lưu giữ địa chỉ thì chương trình có thể truy cập đến địa chỉ của dữ liệu đó, nhưng không xác định được kiểu của nó. Nói cách khác thì con trỏ void được sử dụng để lưu giữ địa chỉ của các kiểu dữ liệu không tồn tại kiểu dữ liệu.

Khai báo con trỏ void trong C++

Cách khai báo con trỏ void trong C++ cũng tương tự như với các loại con trỏ khác, chúng ta viết kiểu void, rồi dấu hoa thị *, và cuối cùng là tên con trỏ void như sau:

void * pdata;

Cách viết này cũng tương tự như với các kiểu con trỏ int hay char chẳng hạn:

char * pCharData;  // Con trỏ kiểu char
int  * pIntData;   // Con trỏ kiểu int

Chúng ta cũng có thể thực hiện các thao tác như gán địa chỉ vào con trỏ, hoặc là in địa chỉ được gán vào con trỏ void tương tự như các loại con trỏ khác trong C++. Ví dụ:

#include <iostream>
using namespace std;

int main()
{
    char data  = 'A'; //Khai báo biến data
    void * pdata = &data; // Khai báo con trỏ void và gán địa chỉ của biến data vào con trỏ

    cout << "pointer: "<< pdata;//In địa chỉ mà con trỏ lưu giữ

    return 0;
}
// pointer: 0x7ffe529fddbf

Tuy nhiên, không giống như với các kiểu con trỏ khác thì chúng ta lại không thể thực hiện các thao tác với giá trị của biến mà con trỏ void trỏ đến, và vì thế cũng không thể biết được kiểu của dữ liệu đó là gì. Ví dụ, chúng ta không thể đọc được giá trị của biến thông qua con trỏ, vì lỗi sau đây sẽ xảy ra:

#include <iostream>
using namespace std;

int main()
{
    char data  = 'A'; //Khai báo biến data
    void * pdata = &data;// Khai báo con trỏ void và gán địa chỉ của biến data vào con trỏ

    cout << "data: "<< *pdata;//In giá trị tại địa chỉ mà con trỏ lưu giữ

    return 0;
}

Vậy chẳng phải con trỏ void trong C++ rất là vô dụng hay sao? Tất nhiên là không phải rồi, vì chúng ta sẽ cần tới con trỏ void trong các trường hợp như dưới đây:

Sử dụng con trỏ void trong C++

Con trỏ void trong C++ sẽ được sử dụng trong các trường hợp đặc biệt sau đây:

Con trỏ vạn năng giúp lưu giữ tất cả các loại giữ liệu trong C++

Một điều dễ hiểu là do con trỏ void không tồn tại kiểu của dữ liệu mà nó đang chỉ đến, nên nó có khả năng chấp nhận và lưu giữ địa chỉ của tất cả các loại giữ liệu khác nhau trong C++. Đây là điều mà các con trỏ khác trong C++ không làm được. Ví dụ như con trỏ kiểu int thì chỉ chấp nhận lưu địa chỉ của dữ liệu kiểu int, còn con trỏ kiểu char thì cũng chỉ có thể chấp nhận lưu giữ địa chỉ của kiểu giữ liệu char. Nhưng với con trỏ void, void chấp hết ^_ .

Ví dụ cụ thể, con trỏ void trong C++ dưới đây có thể lưu giữ địa chỉ của tất cả các loại giữ liệu mà không sợ lỗi xảy ra trong chương trình.

#include <iostream>
using namespace std;

int main(){
    //Khai báo biến
    char   data1;
    short  data2;
    long   data3;
    double data4;

    // Khai báo con trỏ void
    void * pdata;

    //Gán địa chỉ của các loại dữ liệu với nhiều kiểu vào con trỏ void

    pdata = &data1;   // pdata --> data1
    pdata = &data2;   // pdata --> data2
    pdata = &data3;   // pdata --> data3
    pdata = &data4;   // pdata --> data4

    return 0;
}

Con trỏ giúp cố ý ẩn kiểu dữ liệu

Vì con trỏ C++ không cho phép chúng ta đọc kiểu dữ liệu cũng như truy cập vào dữ liệu tại địa chỉ mà nó lưu giữ, nên con trỏ void có vai trò vô cùng quan trong khi chúng ta muốn ẩn kiểu dữ liệu nào đó trong chương trình.

Đây là một thuật toán vô cùng phức tạp đòi hỏi lượng kiến thức khá cao, chỉ dành cho các bạn thực sự pro và muốn tìm hiểu sâu về C++ mà thôi.

Con Trỏ Void Trong C++
Con Trỏ Void Trong C++

XIII. Bài Tập Về Con Trỏ Trong C++

Trong chủ đề này, chúng ta cùng làm một số bài tập về Con trỏ trong C++.

Bài tập 1

Sử dụng con trỏ trong C++, bạn hãy viết một chương trình C++ để nhận dữ liệu từ người dùng và tìm giá trị lớn nhất của một tập dữ liệu nội bộ.

Lời giải

Dưới đây là chương trình C++ để giải bài tập trên. Mình sử dụng một hàm mà nhận mảng các giá trị dữ liệu và kích cỡ của nó. Hàm này trả về con trỏ mà trỏ tới giá trị lớn nhất.

#include<iostream> 
#include<conio.h> 
using namespace std;
int *findMax(int arr[],int n); 
int main(){    
   int n,i,*p;     
  cout<<"Nhap so du lieu: ";     
  cin>>n;     
  int arr[n];         
  for(i=0;i<n;i++)     {       
    cout<<"Nhap gia tri thu "<<i+1<<" la :";       
   cin>>arr[i];             
}           
  p=findMax(arr,n);     
  cout<<"Gia tri lon nhat la: "<<*p;     
  getch();     
  return 0; 
}  
 
int *findMax(int data[],int n){     
  int *max=data;     
  int i;    
 for(i=1;i<n;i++){            
   if(*max<*(max+i)) *max=*(max+i);                              
  }     
  return max; 
}

Chạy chương trình C++ trên sẽ cho kết quả như hình sau:

Bài Tập Về Con Trỏ Trong C++
Bài Tập Về Con Trỏ Trong C++

Bài tập 2

Viết một chương trình C++ để nhận 5 giá trị nguyên từ bàn phím. 5 giá trị này sẽ được lưu trữ trong một mảng bởi sử dụng một con trỏ. Sau đó, in các phần tử của mảng trên màn hình.

Lời giải

Dưới đây là chương trình C++ để giải bài tập trên.

#include<iostream> 
#include<conio.h>
using namespace std;

   int main() 
    { 
     int arr[5],i; 
     int *p=arr; 
     cout<<"Nhap 5 so:"; 
     cin>>*p>>*(p+1)>>*(p+2)>>*(p+3)>>*(p+4); 
     cout<<"Cac so ban vua nhap la:\n"; 
     for(i=0;i<5;i++) 
        cout<<arr[i]<<endl; 
     
     
     return 0; 
  
    }

Chạy chương trình C++ trên sẽ cho kết quả như hình sau:

Bài Tập Về Con Trỏ Trong C++
Bài Tập Về Con Trỏ Trong C++

Sửa đổi lời giải trên để in các phần tử của mảng theo thứ tự đảo ngược bởi sử dụng một con trỏ.

#include<iostream> 
#include<conio.h>
using namespace std;

   int main() 
    { 
     int arr[5],i; 
     int *p=arr; 
     cout<<"Nhap 5 so:"; 
     cin>>*p>>*(p+1)>>*(p+2)>>*(p+3)>>*(p+4); 
     cout<<"Cac so ban vua nhap theo thu tu dao nguoc la:\n"; 
     for(i=4;i>=0;i--) 
        cout<<*(p+i)<<endl; 
     
     
     return 0; 
  
    }

Chạy chương trình C++ trên sẽ cho kết quả như hình sau:

Bài Tập Về Con Trỏ Trong C++
Bài Tập Về Con Trỏ Trong C++

XIV. Ép Kiểu Con Trỏ Trong C++

Ép kiểu trong C++ là việc gán giá trị của một biến có kiểu dữ liệu này tới biến khác có kiểu dữ liệu khác.

Cú pháp:

(type) value;

Ví dụ:

float c = 35.8f;
int b = (int)c + 1;

Trong ví dụ trên, đầu tiên giá trị dấu phảy động c được đổi thành giá trị nguyên 35. Sau đó nó được cộng với 1 và kết quả là giá trị 36 được lưu vào b.

Phân loại ép kiểu trong C++

Trong C++, có hai loại ép kiểu dữ liệu:

  1. Nới rộng (widening): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin.
  2. Thu hẹp (narrowwing): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin

1. Nới rộng (widening)

Ép Kiểu Con Trỏ Trong C++
Ép Kiểu Con Trỏ Trong C++

Nới rộng (widening): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin. Ví dụ chuyển từ int sang float. Chuyển kiểu loại này có thế được thực hiện ngầm định bởi trình biên dịch.

Ví dụ:

#include <iostream>
 
using namespace std;
  
int main() {
    int i = 100;
    long l = i;    // khong yeu cau chi dinh ep kieu
    float f = l;   // khong yeu cau chi dinh ep kieu
    cout << "Gia tri Int: " << i << endl;
    cout << "Gia tri Long: " << l << endl;
    cout << "Gia tri Float: " << f << endl;
    return 0;
}

Kết quả:

Giá trị Int: 100
Giá trị Long: 100
Giá trị Float:  100

2.Thu hẹp (narrowwing)

Ép Kiểu Con Trỏ Trong C++
Ép Kiểu Con Trỏ Trong C++

Thu hẹp (narrowwing): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin như ví dụ ở trên. Chuyển kiểu loại này không thể thực hiện ngầm định bởi trình biên dịch, người dùng phải thực hiện chuyển kiểu tường minh.

Ví dụ:

#include <iostream>
 
using namespace std;
  
int main() {
    double d = 100.04;
    long l = (long) d; // yeu cau chi dinh kieu du lieu (long)
    int i = (int) l; // yeu cau chi dinh kieu du lieu (int)
    cout << "Gia tri Double: " << d << endl;
    cout << "Gia tri Long: " << l << endl;
    cout << "Gia tri Int: " << i << endl;
    return 0;
}

Kết quả:

Giá trị Double: 100.04
Giá trị Long: 100
Giá trị Int:  100

The post Con Trỏ Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/con-tro-trong-c/